Вопросы на собеседовании frontend-разработчика с ответами
Вопросы охватывают язык, браузер и интерфейсы. Проверяйте объяснения на небольших примерах и связывайте их с реальными задачами: загрузкой данных, формами, доступностью и поддержкой приложения.
Современный браузер, Node.js 22 для языковых примеров, React 19. Версию TypeScript фиксирует тестовое окружение проекта.
Вопросы и ответы
Junior: основы
JavaScriptЧем const отличается от let?
Оба объявления имеют блочную область видимости. const запрещает перепривязку переменной, но не делает объект глубоко неизменяемым: свойства и элементы массива могут меняться. let используют, когда переприсваивание является частью алгоритма. Важно также понимать временную мёртвую зону: до выполнения объявления обратиться к такой переменной нельзя. Выбор const по умолчанию уменьшает количество меняющихся связей, но не заменяет проектирование состояния.
- Пример
- Можно выполнить items.push(1) для const items = [], но нельзя затем присвоить items другой массив.
- Частая ошибка
- Считать const гарантией неизменности всех данных объекта.
- Уточнение интервьюера
- Как запретить или контролировать изменение вложенных объектов?
JavaScriptПочему обычно используют ===?
Строгое равенство не выполняет приведение разных типов по правилам нестрогого сравнения. Это делает условие понятнее, особенно при обработке пользовательского ввода. При этом объекты сравниваются по идентичности, а NaN не равен самому себе через ===. Для специфических случаев существует Object.is с другим поведением для NaN и знакового нуля. Сравнение структурных данных требует отдельно выбранной семантики.
- Пример
- Строка "0" и число 0 не равны через ===; два объекта с одинаковыми полями тоже не становятся равными автоматически.
- Частая ошибка
- Использовать JSON.stringify как универсальную проверку равенства любых объектов.
- Уточнение интервьюера
- Когда сравнение объектов по ссылке полезно в React?
JavaScriptЧто такое замыкание?
Функция сохраняет доступ к лексическому окружению места, где она создана. Это позволяет строить обработчики, фабрики и приватное состояние. Замыкание хранит доступ к переменным, а не обязательно фотографию всех значений. В UI это важно при асинхронных обработчиках: код может работать с состоянием конкретного рендера или с изменяемой ссылкой. Чтобы понять поведение, нужно определить, когда функция создана и к какому окружению обращается.
- Пример
- Фабрика счётчика может вернуть функцию, увеличивающую локальную переменную между вызовами.
- Частая ошибка
- Ожидать, что любая отложенная функция автоматически увидит последнее состояние React.
- Уточнение интервьюера
- Как функциональное обновление state помогает избежать устаревшего значения?
JavaScriptЧем map отличается от forEach?
map создаёт массив результатов преобразования, а forEach выполняет действие для каждого элемента и не собирает возвращаемые значения. Если результат преобразования нужен дальше, map явно выражает намерение. Для побочных эффектов обычно понятнее цикл или forEach. Важно не спутать map с ожиданием асинхронных операций: массив promises не является массивом готовых результатов. Порядок и обработка ошибок должны задаваться отдельно.
- Пример
- items.map(load) возвращает promises; await Promise.all(...) ожидает их совместно.
- Частая ошибка
- Писать await items.forEach(async ...) и считать, что все операции завершились.
- Уточнение интервьюера
- Когда последовательный for...of предпочтительнее Promise.all?
HTMLЗачем семантические элементы, если всё можно сделать div?
Семантический элемент сообщает браузеру и вспомогательным технологиям назначение блока и часто даёт встроенное поведение. button поддерживает клавиатурную активацию и состояния, a с href обозначает переход, заголовки формируют структуру документа. Использование div требует вручную воспроизвести эти контракты и легко создаёт неполноценный интерфейс. Семантика также помогает тестировать страницу по ролям вместо случайных CSS-классов.
- Пример
- Для раскрытия дополнительной информации подходят details и summary, если их поведение соответствует задаче.
- Частая ошибка
- Добавить обработчик click на div и забыть клавиатуру и доступное имя.
- Уточнение интервьюера
- Когда кнопка должна быть button, а когда ссылкой?
CSSКогда выбрать Flexbox, а когда Grid?
Flexbox удобно распределяет элементы преимущественно вдоль одной оси, а Grid описывает строки и столбцы общей сетки. Это ориентир, а не запрет на перенос строк у flex. Для карточки с иконкой и текстом часто достаточно flex; для согласованных колонок панели удобен grid. При любом выборе нужно проверить минимальный размер содержимого, перенос длинных строк и поведение на узком экране.
- Пример
- Широкий pre внутри grid может потребовать min-width: 0 у элемента и отдельной прокрутки кода.
- Частая ошибка
- Фиксировать ширину всех колонок в пикселях и скрывать переполнение всей страницы.
- Уточнение интервьюера
- Почему содержимое иногда не сжимается при наличии свободного места в flex?
HTTPЧем HTTP-статус отличается от ошибки fetch?
fetch обычно возвращает Response и для HTTP-ошибок вроде 404 или 500; код должен проверить ok или status. Promise отклоняется при некоторых сетевых ошибках и отмене, но не заменяет проверку прикладного ответа. Формат тела тоже является частью контракта: сервер может вернуть HTML вместо ожидаемого JSON. Поэтому полезно разделить ошибку транспорта, HTTP-отказ, ошибку разбора и бизнес-валидацию.
- Пример
- Перед response.json() можно проверить response.ok и ожидаемый Content-Type.
- Частая ошибка
- Считать успешное разрешение fetch доказательством успешной операции сервера.
- Уточнение интервьюера
- Как показать пользователю ошибку поля отдельно от недоступности сервиса?
ReactЧем props отличаются от state?
Props передаются компоненту извне, state принадлежит конкретному экземпляру компонента. Оба значения нужно рассматривать как снимок текущего рендера, а не изменять напрямую. Если значение вычисляется из props и state, часто его не нужно сохранять ещё в одном state: иначе появятся два расходящихся источника. При общей зависимости нескольких компонентов состояние поднимают к подходящему общему владельцу.
- Пример
- Отфильтрованный список можно вычислить из исходного списка и строки поиска.
- Частая ошибка
- Копировать каждый prop в state и синхронизировать его дополнительным эффектом.
- Уточнение интервьюера
- Когда локальное состояние ввода оправданно отличается от сохранённых данных?
Middle: применение
JavaScriptКак связаны задачи и микрозадачи?
JavaScript выполняет текущую работу до точки завершения; после неё среда обрабатывает очередь микрозадач перед переходом к следующей задаче. Promise callbacks обычно попадают в микрозадачи, таймеры — в очередь задач. Точная работа рендеринга зависит от браузера, поэтому нельзя считать нулевой таймер мгновенным. Длинный синхронный участок блокирует возможность обработать ввод. Бесконечное создание микрозадач также способно мешать дальнейшей работе.
- Пример
- После синхронных console.log обработчик resolved promise обычно выполнится раньше setTimeout(..., 0).
- Частая ошибка
- Считать, что async-функция сразу исполняется в отдельном потоке.
- Уточнение интервьюера
- Как вынести длительные вычисления, сохранив отзывчивость интерфейса?
TypeScriptЧем narrowing отличается от приведения типа?
Narrowing уточняет тип на основании проверок, которые понимает анализатор: typeof, наличие свойства, discriminant или корректный type guard. Утверждение as сообщает компилятору мнение автора, но не проверяет данные во время исполнения. Для внешнего JSON нужен runtime-разбор. Хороший discriminated union позволяет явно обработать состояния загрузки, успеха и ошибки и не носить набор противоречащих друг другу флагов.
- Пример
- У объекта { kind: 'success', data } поле data доступно после проверки kind.
- Частая ошибка
- Писать payload as User и считать, что серверные данные валидированы.
- Уточнение интервьюера
- Как проверять исчерпывающую обработку вариантов union?
ReactКогда нужен useEffect?
Эффект синхронизирует компонент с внешней системой: подпиской, таймером, сторонним виджетом или сетевым взаимодействием. Вычисление значения из уже имеющихся props обычно относится к рендеру, а действие пользователя — к обработчику события. Зависимости эффекта должны соответствовать читаемым реактивным значениям. Cleanup отменяет подписку или устраняет последствия предыдущей синхронизации. В development Strict Mode дополнительные вызовы помогают обнаружить несимметричную настройку.
- Пример
- Подписка на событие добавляется в эффекте и удаляется его cleanup-функцией.
- Частая ошибка
- Отключать проверку зависимостей, чтобы скрыть повторный запуск эффекта.
- Уточнение интервьюера
- Как сделать эффект устойчивым к setup → cleanup → setup?
ReactПочему key не должен быть случайным?
key помогает React сопоставить элемент списка с прежним элементом среди соседей. Стабильный идентификатор сохраняет ожидаемую принадлежность состояния. Случайный key при каждом рендере вынуждает создавать элементы заново. Индекс допустим только в ограниченных ситуациях, когда порядок и состав не меняются так, чтобы состояние перешло другому объекту. Для редактируемого или сортируемого списка обычно нужен идентификатор данных.
- Пример
- После сортировки задач поле ввода должно оставаться связано с той же задачей, а не с её прежней позицией.
- Частая ошибка
- Ставить Math.random() ради исчезновения предупреждения.
- Уточнение интервьюера
- Как key можно намеренно использовать для сброса состояния?
СетьКак избежать показа устаревшего ответа поиска?
При быстром изменении запроса ответы могут прийти в другом порядке. Нужно отменять предыдущую операцию, если транспорт это поддерживает, и отдельно защищать применение результата актуальным идентификатором запроса. Отмена экономит ресурсы, а проверка актуальности защищает состояние даже там, где отмена не сработала. Ошибку отмены обычно не показывают как сбой сервиса. Loading и error тоже должны относиться к текущей операции.
- Пример
- Результат поиска «py» не должен перезаписать уже показанный результат «python».
- Частая ошибка
- Считать debounce достаточной защитой от любого изменения порядка ответов.
- Уточнение интервьюера
- Как проверить гонку детерминированным тестом?
ДоступностьЧто проверить у диалога?
Диалог должен иметь доступное имя, понятный способ закрытия и управляемый фокус. При открытии фокус перемещают внутрь подходящим образом, а после закрытия возвращают к инициатору, если он ещё существует. Фон не должен оставаться случайно доступным для взаимодействия при модальном режиме. Нужно проверить клавиатуру, Escape, чтение заголовка и прокрутку на маленьком экране. ARIA-атрибуты не заменяют фактического поведения.
- Пример
- Пользователь клавиатуры может открыть диалог, выполнить действие и вернуться к исходной кнопке.
- Частая ошибка
- Добавить role='dialog', оставив фокус за пределами окна.
- Уточнение интервьюера
- Когда предпочтительнее немодальный блок вместо диалога?
ПроизводительностьКак изображения влияют на LCP и CLS?
Крупное изображение первого экрана может стать LCP-элементом, поэтому важны его размер, формат и своевременная загрузка. Заданные пропорции или размеры резервируют место и уменьшают сдвиги макета. Lazy loading полезен для изображений ниже экрана, но может задержать ключевое изображение первого экрана. Адаптивные варианты позволяют не отправлять телефону избыточную картинку. Решение проверяют измерением, а не только наличием атрибута loading.
- Пример
- Карточки ниже первого экрана загружаются отложенно, а hero получает подходящий размер и зарезервированную область.
- Частая ошибка
- Включить lazy для всех изображений и считать оптимизацию законченной.
- Уточнение интервьюера
- Как отличить задержку загрузки ресурса от задержки его отображения?
ТестированиеКак тестировать компонент без привязки к вёрстке?
Начните с доступного поведения: найти элемент по роли и имени, выполнить действие, проверить видимый результат. CSS-классы и дерево внутренних компонентов меняются чаще пользовательского контракта. Асинхронные изменения нужно ожидать по наблюдаемому состоянию, а не фиксированным sleep. Unit-тест компонента не заменяет браузерную проверку layout, фокуса и настоящего сетевого взаимодействия. Разные уровни тестов должны закрывать разные риски.
- Пример
- Найти кнопку «Сохранить», нажать и дождаться сообщения об успешном сохранении.
- Частая ошибка
- Проверять приватный state вместо того, что видит пользователь.
- Уточнение интервьюера
- Что jsdom не доказывает о поведении реального браузера?
Senior: компромиссы и надёжность
АрхитектураКак выбирать границу серверного и клиентского UI?
На сервере удобно получать данные и формировать первоначальный HTML, а клиент нужен для взаимодействия с состоянием браузера. Граница влияет на объём JavaScript и на то, какие данные сериализуются наружу. Секреты и служебные объекты не должны попадать в props клиентского компонента. Большая клиентская оболочка может незаметно затянуть в bundle лишние зависимости. Начинать полезно с минимального интерактивного островка вокруг конкретного действия.
- Пример
- Текст статьи отдаётся сервером, а кнопка копирования кода работает в небольшом клиентском компоненте.
- Частая ошибка
- Помечать всю страницу use client только ради одной кнопки.
- Уточнение интервьюера
- Как проверить, какие зависимости оказались в клиентском bundle?
ДанныеЧто важно при кешировании интерфейса?
Нужно определить ключ, срок жизни, условия обновления и допустимую устарелость. Кеш авторизованных данных должен учитывать пользователя или быть изолирован от общего кеша. После мутации требуется обновить именно зависимые представления, иначе пользователь увидит противоречивые списки и карточки. Ошибка загрузки не всегда означает, что старые данные нужно стереть. Для SEO-страниц особенно важно согласовать опубликованное содержимое, навигацию и sitemap.
- Пример
- После снятия статьи с публикации она должна исчезнуть и из списка, и из публичного маршрута.
- Частая ошибка
- Кешировать персональный ответ под общим ключом URL.
- Уточнение интервьюера
- Как сочетать оптимистическое обновление с серверным отказом?
БезопасностьПочему экранирование HTML не решает все риски UI?
Экранирование текста предотвращает интерпретацию части данных как разметки, но остаются другие контексты: URL, CSS, HTML-вставки и сообщения между окнами. Для ссылок нужно ограничивать допустимые схемы, для rich text — разрешённые узлы, для postMessage — происхождение и структуру. Данные из CMS или внешнего API также считаются входом. Защита должна соответствовать месту использования, а не названию источника.
- Пример
- Обычный текст безопаснее рендерить как текст; вставка HTML требует отдельной обработки разрешённого содержимого.
- Частая ошибка
- Считать любого авторизованного редактора основанием разрешить произвольный script.
- Уточнение интервьюера
- Чем защита от XSS отличается от защиты от CSRF?
ПроизводительностьКак расследовать медленное взаимодействие?
Определите конкретное действие и измерьте его в профиле браузера на устройстве, похожем на пользовательское. Отделите обработчик, вычисления, React render, layout, paint и ожидание сети. Мемоизация помогает только при подходящей причине и имеет собственную стоимость. Длинную работу можно дробить или переносить в worker; лишние обновления — сокращать изменением границ состояния. Итог проверяют повторным измерением того же сценария, включая ошибки и медленную сеть.
- Пример
- Большая фильтрация на каждом нажатии может требовать изменения алгоритма, а не useMemo вокруг каждого компонента.
- Частая ошибка
- Оптимизировать по числу рендеров без измерения времени и пользовательского эффекта.
- Уточнение интервьюера
- Какой риск добавляет виртуализация длинного списка для доступности?
Практические задания
1. Сгруппировать без изменения входа
Сгруппировать записи по строковому полю role, сохранив порядок внутри группы. Корректно обработать ключ __proto__.
Ожидаемый результат. Две записи python остаются в исходном порядке; пустой вход даёт пустой результат.
Решение и объяснение
Map не использует пользовательские ключи как свойства прототипа. Создаём новые массивы, не меняя исходный список.
export function groupByRole(items) {
const result = new Map();
for (const item of items) {
const group = result.get(item.role) ?? [];
group.push(item);
result.set(item.role, group);
}
return result;
}2. Применять только актуальный результат
Создать контроллер поиска, отменяющий предыдущую загрузку и игнорирующий её запоздалый ответ.
Ожидаемый результат. Если второй запрос завершился раньше первого, UI сохраняет второй результат; отмена не показывается как ошибка.
Решение и объяснение
AbortController экономит работу, а номер запуска защищает от транспорта, который проигнорировал signal. load принимает запрос и signal, apply/error — функции обновления UI.
export function createSearch(load, apply, onError) {
let version = 0;
let controller;
return async (query) => {
const current = ++version;
controller?.abort();
controller = new AbortController();
try {
const result = await load(query, controller.signal);
if (current === version) apply(result);
} catch (error) {
if (current === version && error?.name !== 'AbortError') onError(error);
}
};
}3. Доступное раскрытие ответа
Создать компонент React 19 с вопросом и раскрываемым ответом. Он должен работать с клавиатуры и иметь текст в серверном HTML.
Ожидаемый результат. Summary получает фокус; Enter или Space переключает раскрытие; ответ присутствует в документе до взаимодействия.
Решение и объяснение
Нативные details/summary предоставляют нужное поведение без собственного обработчика клавиш и скрытой сетевой загрузки. Не помещайте в summary дополнительные конфликтующие кнопки.
export function Answer({ question, children }) {
return (
<details>
<summary>{question}</summary>
<div>{children}</div>
</details>
);
}План подготовки на семь дней
- Выпишите требования вакансии и отметьте знакомые и незнакомые темы.
- Ответьте на вопросы Junior вслух, затем проверьте себя по объяснениям.
- Разберите вопросы Middle. Для каждой ошибки запишите свой небольшой пример.
- Решите практические задания без подсказок и проверьте граничные случаи.
- Разберите вопросы Senior и объясните компромиссы применительно к своему проекту.
- Проведите пробное интервью: уточняйте условия, рассуждайте и проверяйте выводы.
- Повторите ошибки, подготовьте реальные истории опыта и проверьте технику.