Перейти к содержанию
Фронтенд

10 вопросов по теме «Frontend: Производительность» на собеседовании

В этом материале — 10 вопросов из русской колоды RecallDeck по теме «Frontend: Производительность». Сначала сформулируйте короткий ответ сами, затем откройте подробный разбор и проверьте примеры, ограничения и отказные случаи.

9 мин чтения10 подробных ответовПроверено 24 августа 2026
Главная мысль

Связывайте ответ с браузером, доступностью, производительностью и пользовательским состоянием — именно там видна зрелость frontend-инженера.

Вопросы и ответы

10 подробных ответов

01

Что такое Critical Rendering Path и какие ресурсы его блокируют?

Короткий ответ: Critical Rendering Path (критический путь рендеринга) — это последовательность шагов браузера от получения HTML до первого пикселя на экране: DOM + CSSOM → render tree → layout → paint → composite. Синхронный CSS и блокирующий JS задерживают этот путь.

Подробно:

  1. DOM — браузер парсит HTML в дерево узлов.
  2. CSSOM — параллельно строится объектная модель стилей; CSS блокирует рендеринг, так как без него нельзя посчитать стили.
  3. Render tree — DOM и CSSOM объединяются, узлы с display: none отбрасываются.
  4. Layout (reflow) — вычисляются геометрия и позиции элементов.
  5. Paint — узлы растеризуются в слои пикселей.
  6. Composite — слои собираются на экране (часто на GPU).
HTML ─► DOM ─┐
             ├─► Render Tree ─► Layout ─► Paint ─► Composite ─► экран
CSS  ─► CSSOM┘
  ▲ блокирует рендеринг      ▲ <script> без defer/async

⚠️ Частая ошибка: считать, что JS никогда не мешает рендерингу. Синхронный <script> в <head> останавливает парсинг HTML и построение DOM — используйте defer или вынесите скрипты вниз.

02

В чём разница между reflow, repaint и composite, и какие CSS-свойства дёшево анимировать?

Короткий ответ: Reflow (layout) пересчитывает геометрию, repaint перерисовывает пиксели без изменения геометрии, composite лишь пересобирает уже отрисованные слои. Дёшево анимировать только transform и opacity — они идут на этап composite, минуя layout и paint.

Подробно:

  1. Reflow — самый дорогой: меняет размеры/позиции (width, top, margin, добавление узлов) и каскадно затрагивает потомков и соседей.
  2. Repaint — перерисовка без сдвига геометрии (color, background, visibility).
  3. Composite — GPU объединяет готовые слои; layout и paint не запускаются.
Свойство Этап Стоимость
width, top, margin layout → paint → composite высокая
color, background paint → composite средняя
transform, opacity composite низкая

⚠️ Частая ошибка: анимировать left/top или width/height вместо transform: translate() / scale() — это вызывает reflow на каждый кадр и роняет FPS.

03

Что такое Core Web Vitals (LCP, INP, CLS), какие у них пороги и как их улучшать?

Короткий ответ: Core Web Vitals — три ключевые метрики Google: LCP (загрузка), INP (отзывчивость) и CLS (визуальная стабильность). С марта 2024 INP официально заменил FID как метрику отзывчивости.

Подробно:

  1. LCP (Largest Contentful Paint) — время отрисовки самого крупного элемента. Улучшается приоритизацией LCP-изображения (fetchpriority="high", preload), быстрым TTFB, отказом от блокирующих ресурсов.
  2. INP (Interaction to Next Paint) — задержка от взаимодействия до следующего кадра по всем интеракциям за сессию. Улучшается разбиением длинных задач, requestIdleCallback, меньшим объёмом JS.
  3. CLS (Cumulative Layout Shift) — суммарный сдвиг макета. Улучшается резервированием места под изображения (width/height), font-display, отсутствием вставок над контентом.
Метрика Что измеряет Хорошо Плохо
LCP загрузка ≤ 2.5 с > 4.0 с
INP отзывчивость ≤ 200 мс > 500 мс
CLS стабильность ≤ 0.1 > 0.25

⚠️ Частая ошибка: называть FID действующей метрикой. FID убрали в марте 2024 — отзывчивость теперь измеряет INP, который учитывает всю интеракцию, а не только первую задержку.

04

Чем отличаются defer и async у скриптов, и зачем нужны preload, prefetch и preconnect?

Короткий ответ: async грузит скрипт параллельно и выполняет сразу по готовности (порядок не гарантирован); defer грузит параллельно, но выполняет по порядку после парсинга DOM. preload — приоритетная загрузка ресурса для текущей страницы, prefetch — фоновая для будущих, preconnect — заранее открыть соединение.

Подробно:

  1. defer — для скриптов, зависящих от DOM и друг от друга; сохраняет порядок, не блокирует парсинг.
  2. async — для независимых скриптов (аналитика); выполняется как только загрузился.
  3. preload — «нужно сейчас»: шрифт, LCP-картинка, критичный CSS.
  4. prefetch — «понадобится потом»: ресурсы следующей навигации, с низким приоритетом.
  5. preconnect — заранее выполнить DNS + TLS-handshake к стороннему домену (CDN, шрифты).
<script src="app.js" defer></script>
<script src="analytics.js" async></script>

<link rel="preload" href="hero.avif" as="image" fetchpriority="high">
<link rel="prefetch" href="/next-page.js">
<link rel="preconnect" href="https://cdn.example.com">

<img src="below-fold.jpg" loading="lazy" alt="...">

⚠️ Частая ошибка: ставить async скриптам, которые зависят от порядка загрузки — выполнение «кто первый» ломает зависимости. Для таких случаев нужен defer.

05

Как уменьшить размер бандла: code splitting, dynamic import(), tree shaking?

Короткий ответ: Грузите меньше JS наперёд: разбивайте бандл по маршрутам/компонентам через динамический import() (code splitting), удаляйте мёртвый код через tree shaking (требует ESM-модулей), и анализируйте состав бандла визуализатором.

Подробно:

  1. Code splitting — делите код на чанки, грузимые по требованию; снижает первоначальную загрузку.
  2. Dynamic import() — ленивая подгрузка модуля/компонента в момент необходимости (роут, модалка).
  3. Tree shaking — сборщик отбрасывает неиспользуемые экспорты. Работает только с import/export (ESM), не с CommonJS require, и ломается о побочные эффекты — помогает "sideEffects": false.
  4. Анализrollup-plugin-visualizer / webpack-bundle-analyzer показывают, что раздувает бандл (часто тяжёлые библиотеки дат/иконок).
// Статический импорт — попадает в основной бандл
import heavyChart from "chart-lib";

// Динамический — выделяется в отдельный чанк, грузится по требованию
const Chart = React.lazy(() => import("./Chart"));

button.addEventListener("click", async () => {
  const { renderModal } = await import("./modal.js");
  renderModal();
});

⚠️ Частая ошибка: надеяться на tree shaking при импорте всей библиотеки (import _ from "lodash") или из CommonJS-сборки — импортируйте точечно (import debounce from "lodash/debounce").

06

Как работает кэширование в браузере: Cache-Control, ETag, хешированные имена файлов и Service Worker?

Короткий ответ: HTTP-кэш управляется заголовками Cache-Control (на сколько и как кэшировать) и валидаторами ETag/Last-Modified (проверка свежести через 304). Хеш в имени файла (app.a1b2c3.js) обеспечивает cache busting, а Service Worker даёт программный кэш для офлайна.

Подробно:

  1. Cache-Controlmax-age задаёт TTL; immutable отключает повторную валидацию; no-cache означает «кэшируй, но проверяй».
  2. ETag / Last-Modified — при истечении TTL браузер шлёт If-None-Match; сервер отвечает 304 Not Modified без тела, экономя трафик.
  3. Хешированные имена — меняется контент → меняется имя → старый кэш игнорируется. Поэтому ассеты раздают с max-age=31536000, immutable, а HTML — no-cache.
  4. Service Worker — перехватывает запросы (fetch), реализует стратегии (cache-first, stale-while-revalidate), работает офлайн.
Cache-Control: public, max-age=31536000, immutable   # app.a1b2c3.js
Cache-Control: no-cache                               # index.html
ETag: "5d8c72a..."

⚠️ Частая ошибка: ставить долгий max-age на HTML без хеша — пользователи застревают на старой версии. Хешируйте ассеты, а entry-point HTML держите на no-cache.

07

Как эффективно рендерить большие списки и что такое виртуализация (windowing)?

Короткий ответ: Виртуализация (windowing) рендерит только видимые в окне просмотра строки плюс небольшой буфер, а не все тысячи DOM-узлов. Это держит число узлов и время layout/paint константными независимо от длины списка.

Подробно:

  1. Проблема — 10 000 строк = 10 000 DOM-узлов: огромный layout, медленный paint, раздутая память, лаги при скролле.
  2. Идея — контейнер задаёт полную высоту (itemCount × itemHeight), но в DOM существуют только видимые строки, спозиционированные через transform: translateY.
  3. Окно (window) — при скролле пересчитывается видимый диапазон индексов; за пределами viewport рендерится небольшой overscan-буфер, чтобы скролл был плавным.
  4. Инструментыreact-window / @tanstack/virtual для React; для динамической высоты строк нужна оценка и измерение.
  Реальный список            Виртуализированный DOM
┌───────────────┐          ┌───────────────┐ ← scroll container
│ row 0         │          │ row 142       │  (только видимое
│ ...           │          │ row 143       │   окно + overscan)
│ row 9999      │          │ row 144       │
└───────────────┘          └───────────────┘
  10000 узлов                ~12 узлов в DOM

⚠️ Частая ошибка: пытаться «починить» лаги большого списка через React.memo и пагинацию, оставив все узлы в DOM. Узкое место — размер DOM и layout; решает именно виртуализация.

08

Как оптимизировать изображения и шрифты: WebP/AVIF, srcset/sizes и font-display?

Короткий ответ: Используйте современные форматы (AVIF/WebP вместо JPEG/PNG), отдавайте под размер экрана через srcset/sizes (responsive images), а для шрифтов задавайте font-display: swap, чтобы избежать FOIT (невидимый текст), и preload для критичных шрифтов.

Подробно:

  1. Форматы — AVIF сжимает сильнее всего, WebP — широкая поддержка; <picture> даёт фолбэк на JPEG.
  2. Responsive imagessrcset перечисляет варианты по ширине, sizes подсказывает браузеру отображаемый размер, чтобы выбрать минимально достаточный файл.
  3. font-displayswap сразу показывает системный шрифт и подменяет на загруженный (FOUT вместо FOIT); optional экономнее на медленных сетях.
  4. Стабильность — задавайте width/height или aspect-ratio, чтобы не было сдвига макета (CLS).
Формат Прозрачность Сжатие Когда
AVIF да лучшее hero, фото
WebP да хорошее дефолт + фолбэк
SVG да вектор иконки, лого
<picture>
  <source type="image/avif" srcset="hero-480.avif 480w, hero-960.avif 960w" sizes="(max-width:600px) 480px, 960px">
  <img src="hero.jpg" width="960" height="540" alt="...">
</picture>
@font-face { font-family: Inter; src: url(inter.woff2) format("woff2"); font-display: swap; }

⚠️ Частая ошибка: грузить один огромный JPEG для всех экранов. Без srcset мобильные устройства качают десктопную версию, раздувая LCP и трафик.

09

Как измерять производительность: Lighthouse, Performance API / web-vitals и разница lab vs field (RUM)?

Короткий ответ: Lighthouse и DevTools дают лабораторные (lab) данные — синтетический прогон в контролируемых условиях. Performance API и библиотека web-vitals собирают полевые (field/RUM) данные с реальных пользователей. Lab нужен для отладки, field — для оценки реального опыта.

Подробно:

  1. Lab (лабораторные) — Lighthouse, WebPageTest: воспроизводимо, с заданным троттлингом сети/CPU; удобно ловить регрессии в CI, но не отражает разнообразие реальных устройств.
  2. Field / RUM — данные с настоящих пользователей (CrUX, web-vitals): отражают реальные сети, девайсы, географию. INP и CLS корректно меряются только в поле, по всей сессии.
  3. Performance APIPerformanceObserver, performance.getEntriesByType('navigation'), paint — низкоуровневый доступ к таймингам.
  4. web-vitals — обёртка для отправки LCP/INP/CLS в аналитику.
import { onLCP, onINP, onCLS } from "web-vitals";

function send(metric) {
  navigator.sendBeacon("/rum", JSON.stringify(metric));
}
onLCP(send);
onINP(send);
onCLS(send);
Lab Field (RUM)
Источник синтетика реальные юзеры
Инструмент Lighthouse web-vitals / CrUX
Польза отладка, CI реальный опыт

⚠️ Частая ошибка: оптимизировать только по 100/100 в Lighthouse. Lab-прогон не покажет реальный INP — у пользователей на слабых телефонах метрики могут быть совсем другими.

10

Как улучшить runtime-производительность скролла: requestAnimationFrame, debounce/throttle, passive-слушатели?

Короткий ответ: Синхронизируйте визуальные обновления с кадрами через requestAnimationFrame, ограничивайте частоту обработчиков scroll/resize через throttle/debounce, помечайте слушатели { passive: true }, чтобы не блокировать скролл, и дробите длинные задачи (>50 мс), чтобы не страдал INP.

Подробно:

  1. requestAnimationFrame — выполняет колбэк перед следующей перерисовкой (~60 fps), избегая лишних layout и разрывов кадра.
  2. throttle / debounce — throttle ограничивает частоту вызова (скролл), debounce ждёт паузы (поиск, resize).
  3. passive: true — обещает не вызывать preventDefault, поэтому браузер скроллит, не дожидаясь обработчика.
  4. Длинные задачи — задачи дольше 50 мс блокируют main thread и портят INP; разбивайте их (scheduler.yield, setTimeout).
let ticking = false;
window.addEventListener("scroll", () => {
  if (ticking) return;
  ticking = true;
  requestAnimationFrame(() => {
    updateHeader(window.scrollY); // читаем/пишем layout один раз за кадр
    ticking = false;
  });
}, { passive: true });

⚠️ Частая ошибка: делать тяжёлые вычисления и чтение layout (offsetTop) прямо в обработчике scroll без rAF и throttle — это вызывает forced reflow на каждое событие и убивает плавность.

Источники

Источники и редакционная политика

Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.

От чтения к воспроизведению

Отрепетируйте полный цикл интервью.

RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.

Начать подготовку

Продолжить подготовку

Библиотека собеседований RecallDeck

Подробные русские ответы, разборы этапов найма и планы подготовки для российского IT-рынка.

RSS