Подготовка к frontend-собеседованию

Тренажёр учит не угадывать ответ, а объяснять его своими словами: коротко, по существу и с готовностью к уточняющим вопросам. Вопросы охватывают HTML, CSS, JavaScript, TypeScript, браузер, сети и доступность, а также архитектуру фронтенда, тестирование, React, управление состоянием, сборку проектов и производительность. Два уровня: Junior и Middle и выше.

Как пользоваться

Подготовка — круг из случайных вопросов по выбранной теме или по всем сразу: 10, 20, 30 или 40, по умолчанию 10. Ответьте вслух или запишите тезисы, затем откройте разбор: короткий ответ, подробное объяснение, уточняющие вопросы и частые ошибки.

Собеседование — вопросы из разных тем с таймером, ориентир — 2 минуты на ответ: 20 вопросов займут около 40 минут, как настоящее собеседование. Разборы откроются после завершения, там же итог по темам.

Повторение — вопросы, к которым пора вернуться. После разбора отметьте в чек-листе, что прозвучало в вашем ответе. Если отмечено 80% пунктов и больше, вопрос снова появится в этом режиме через 3 дня, потом через 7, 14 и 30 дней; если меньше — уже завтра. Так вопросы, которые вы знаете хуже, попадаются чаще.

Ответы автоматически не проверяются: чек-лист — это ваша самооценка. Записи и расписание повторения хранятся только в этом браузере.

Для вашего удобства рекомендую использовать полноэкранный режим.

Выберите режим и уровень.

Привет, я Тони, сегодня я ваш интервьюер. Отвечайте своими словами, вслух или тезисами, а потом сверьтесь с разбором. Выберите режим и начнём.

Настройка подхода
Режим
Уровень

HTML и формы · Junior

Какие пары попадут в new FormData(form) для этой формы? Объясните роль name, disabled, readonly и состояния checkbox.

                        <form>
  <input name="user" value="Ира">
  <input value="без имени">
  <input name="role" value="admin" disabled>
  <input name="city" value="Пермь" readonly>
  <input type="checkbox" name="agree" value="yes">
</form>
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как отправить значение поля, которое нельзя редактировать?
  • Как сервер узнает, что галочка снята?

Разбор

Результат содержит user=Ира и city=Пермь. Значение без name не имеет ключа для отправки. disabled исключает поле из данных формы, а readonly лишь запрещает редактирование поддерживаемого поля. Неотмеченный checkbox не добавляет пару, поэтому отсутствие agree нельзя автоматически трактовать как строку «false». На сервере отсутствие поля обрабатывают явно.

Уточняющие вопросы

Как отправить значение поля, которое нельзя редактировать?

Сделать его readonly, а не disabled, или продублировать значение в <input type="hidden">. Сервер всё равно должен проверять такое значение: пользователь может его изменить.

Как сервер узнает, что галочка снята?

По отсутствию поля в данных. Если нужно явное значение, сервер считает отсутствие как «нет», а не ждёт строку false.

Что прозвучало в вашем ответе
  • Названы user=Ира и city=Пермь; другие поля исключены.
  • name определяет ключ, readonly не исключает поле, disabled исключает.
  • Неотмеченный checkbox не передаёт значение.

Почитать на сайте

Спецификация: WHATWG: данные и проверка формы

HTML и формы · Junior

Форма проверяет email через type="email" и required. Можно ли убрать проверку на сервере? Что делать с серверной ошибкой после отправки?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как обойти проверку браузера?
  • Что делать, если почта корректная по формату, но уже занята?

Разбор

Нет. Пользователь может изменить страницу или отправить запрос без формы. Браузерная проверка ловит часть ошибок раньше, но не подтверждает существование адреса и не проверяет права пользователя. Сервер повторно проверяет входные данные и бизнес-условия. Его ответ нужно показать понятным сообщением и дать исправить форму без потери ввода.

Уточняющие вопросы

Как обойти проверку браузера?

Удалить атрибут required в DevTools, добавить форме novalidate или отправить запрос напрямую, без формы, например через curl.

Что делать, если почта корректная по формату, но уже занята?

Это может проверить только сервер. Он возвращает ошибку для конкретного поля, а интерфейс показывает её рядом с полем почты и переводит туда фокус.

Что прозвучало в вашем ответе
  • Проверка браузера помогает пользователю, но обходится.
  • Сервер проверяет данные и правила операции независимо.
  • Ошибка показывается рядом с полем; введённые значения сохраняются.

Почитать на сайте

Спецификация: WHATWG: проверка формы на клиенте

HTML и формы · Junior

Зачем в начале документа нужен <!DOCTYPE html> и что обязательно должно быть в <head>?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что будет, если не указать кодировку?
  • Чем title отличается от h1?
  • Можно ли подключать скрипты в head?

Разбор

DOCTYPE — не тег, а указание браузеру, по каким правилам разбирать страницу. Строка <!DOCTYPE html> включает стандартный режим. Без неё браузер имитирует поведение старых версий: например, иначе считает высоту строк и размеры блоков, и одна и та же вёрстка выглядит по-разному.

В <head> находятся сведения о документе, которые не выводятся как содержимое. Минимум: <meta charset="utf-8"> в самом начале, чтобы кириллица не превратилась в «кракозябры», и <title> — название вкладки и заголовок в поисковой выдаче. Почти всегда нужны viewport для мобильных, описание description, подключение стилей и иконка сайта. Атрибут lang у <html> сообщает язык страницы скринридерам и переводчикам.

Уточняющие вопросы

Что будет, если не указать кодировку?

Кодировка может быть объявлена HTTP-заголовком или BOM. Если этих сведений нет, важен <meta charset="utf-8">, полностью размещённый в первых 1024 байтах. Полагаться на угадывание не стоит.

Чем title отличается от h1?

<title> — название документа: вкладка, закладки, поисковая выдача. На самой странице он не виден. <h1> — главный заголовок содержимого, который видит читатель. Тексты могут совпадать, но title обычно короче и часто включает название сайта.

Можно ли подключать скрипты в head?

Можно, но обычный <script> в <head> остановит разбор страницы, пока не загрузится и не выполнится. Поэтому скрипты либо ставят в конец <body>, либо подключают в <head> с атрибутом defer.

Что прозвучало в вашем ответе
  • DOCTYPE включает стандартный режим, без него браузер работает в режиме совместимости.
  • В head находятся сведения о документе, а не видимое содержимое.
  • Назван meta charset (utf-8) в начале head.
  • Назван title и его назначение: вкладка и поисковая выдача.
  • Упомянут meta viewport для мобильных устройств.

Почитать на сайте

HTML и формы · Junior

Что такое семантическая разметка? Зачем писать <header>, <nav>, <main> и <article>, если всё можно сделать на <div>?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем article отличается от section?
  • Сколько может быть header и main на странице?
  • Как семантика связана с заголовками?

Разбор

Внешне <nav> и <div> ничем не отличаются: оба блочные и без оформления. Разница в смысле. Семантические теги создают ориентиры (landmarks): скринридер позволяет сразу перейти к основному содержимому <main> или к навигации <nav>. Поисковики лучше понимают, какой текст главный. Разработчик видит структуру страницы, не разбираясь в классах.

Основные теги: <header> и <footer> — шапка и подвал страницы или раздела; <nav> — основные блоки ссылок навигации; <main> — уникальное содержимое страницы, один на странице; <article> — самостоятельный материал, который имеет смысл и отдельно: статья, карточка товара, комментарий; <section> — тематический раздел, обычно с заголовком; <aside> — дополнительное содержимое сбоку. <div> остаётся для обёрток, которые нужны только для оформления.

Уточняющие вопросы

Чем article отличается от section?

<article> — законченный материал, который можно вынести на другую страницу или в RSS, и он останется понятным. <section> — часть чего-то большего, тематический раздел со своим заголовком. Статья может состоять из секций, а секция «Новости» — из статей.

Сколько может быть header и main на странице?

<main> — один видимый на странице. <header> и <footer> могут быть у всей страницы и у отдельных статей или разделов.

Как семантика связана с заголовками?

Структуру документа задают в первую очередь заголовки <h1>–<h6>: скринридеры позволяют переходить по ним. Разделы <section> обычно начинаются с заголовка, а уровни идут по порядку, без пропусков ради размера шрифта.

Что прозвучало в вашем ответе
  • Семантика — выбор тега по смыслу содержимого, а не по виду.
  • Названа польза для скринридеров: ориентиры и навигация по областям.
  • Названа польза для поисковиков и читаемости кода.
  • Правильно описаны хотя бы три тега: header, nav, main, article, section, aside, footer.
  • div остаётся для обёрток без смысловой роли.

Почитать на сайте

HTML и формы · Junior

Чем блочные элементы отличаются от строчных? Приведите примеры и объясните, почему у <span> не работает width.

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что даёт display: inline-block?
  • Можно ли вложить блочный элемент в ссылку?

Разбор

Блочные по умолчанию: <div>, <p>, заголовки, списки, <section>. Они выстраиваются сверху вниз, занимают всю доступную ширину, принимают width, height и все отступы.

Строчные: <span>, <a>, <strong>, <em>, <code>. Они живут внутри строки текста и переносятся вместе с ним. Ширина строчного элемента — это ширина его текста, поэтому width игнорируется. Горизонтальные padding и margin работают, а вертикальные padding рисуются, но не раздвигают соседние строки.

Это поведение задаёт CSS-свойство display, и его можно поменять. display: inline-block оставляет элемент в строке, но позволяет задать размеры — так делают кнопки и бейджи. Элементы <img> и <input> строчные, но у них есть собственные размеры, поэтому width для них работает.

Уточняющие вопросы

Что даёт display: inline-block?

Элемент стоит в строке рядом с текстом, как inline, но принимает width, height и вертикальные отступы, как блок. Между такими элементами появляется небольшой зазор от пробелов в HTML-коде.

Можно ли вложить блочный элемент в ссылку?

Да, в HTML5 ссылка <a> может оборачивать блоки, например целую карточку. Нельзя только вкладывать интерактивные элементы: ссылку в ссылку или кнопку в ссылку. А вот <p> не может содержать блоки вроде <div>.

Что прозвучало в вашем ответе
  • Блочный элемент начинается с новой строки и занимает всю ширину.
  • Строчный идёт внутри текста, размер по содержимому.
  • width и height не действуют на обычный строчный элемент.
  • Тип определяется свойством display и меняется в CSS.
  • Назван inline-block как промежуточный вариант.

Почитать на сайте

HTML и формы · Junior

Как правильно расставлять заголовки <h1>–<h6>? Можно ли взять <h4>, потому что он подходит по размеру?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Можно ли несколько h1 на странице?
  • Логотип в шапке — это h1?

Разбор

Уровень заголовка — это его место в иерархии. Страница обычно начинается с одного <h1> с основной темой. Разделы — <h2>, их подразделы — <h3> и так далее. Переходить вниз можно только на один уровень: после h2 идёт h3, а не h5. Вверх можно возвращаться на любой уровень.

Пользователи скринридеров часто просматривают страницу по списку заголовков, как по оглавлению. Если уровни выбраны по размеру шрифта, структура для них ломается. Внешний вид заголовка меняют в CSS: h2 { font-size: 1.25rem; }. А если текст лишь выглядит крупным, но не открывает раздел, это не заголовок, а абзац с классом.

Уточняющие вопросы

Можно ли несколько h1 на странице?

Технически HTML это не запрещает, но на практике рекомендуют один <h1> с главной темой страницы: так понятнее и людям, и поисковикам. Браузеры не меняют уровни заголовков внутри section автоматически.

Логотип в шапке — это h1?

Обычно нет: логотип повторяется на каждой странице и не описывает её тему. h1 — заголовок конкретной страницы: название статьи, товара, раздела. Исключение иногда делают для главной страницы.

Что прозвучало в вашем ответе
  • Заголовки задают иерархию документа, как оглавление.
  • Один h1 с темой страницы.
  • Уровни идут по порядку, без пропусков вниз.
  • Размер меняется в CSS, а не выбором другого уровня.
  • Упомянуты скринридеры: навигация по заголовкам.

Почитать на сайте

HTML и формы · Junior

Какие атрибуты стоит указывать у <img> и зачем? Зачем писать width и height, если размер всё равно задаётся в CSS?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что писать в alt у фотографии товара?
  • Когда картинку лучше сделать фоном в CSS?

Разбор

src — адрес изображения. alt — текст, который прочитает скринридер и который покажется, если картинка не загрузилась. Он описывает смысл изображения в контексте, а у чисто декоративной картинки должен быть пустым: alt="". Отсутствие alt совсем — ошибка: скринридер может прочитать имя файла.

width и height в HTML не мешают CSS: правило img { max-width: 100%; height: auto; } по-прежнему делает картинку резиновой. Зато браузер ещё до загрузки файла вычисляет пропорции и резервирует место. Без них текст сначала подтягивается вверх, а потом резко сдвигается вниз, когда картинка загрузилась. Это раздражает и ухудшает метрику CLS.

loading="lazy" откладывает загрузку картинок, до которых пользователь ещё не долистал. Для главной картинки первого экрана его не ставят, чтобы она появилась как можно раньше.

Уточняющие вопросы

Что писать в alt у фотографии товара?

То, что важно в контексте: «Красный рюкзак Urban 20 л, вид спереди». Не нужно начинать с «Картинка…» — скринридер и так сообщит, что это изображение.

Когда картинку лучше сделать фоном в CSS?

Когда она чисто декоративная и не несёт смысла: узор, фоновая текстура. Если изображение — часть содержимого (фото товара, схема), его вставляют через <img> с alt.

Что прозвучало в вашем ответе
  • src и alt названы обязательными, объяснено назначение alt.
  • У декоративной картинки alt пустой.
  • width и height резервируют место и задают пропорции до загрузки.
  • Упомянут сдвиг содержимого при загрузке (CLS).
  • Назван loading="lazy" и когда его не ставить.

Почитать на сайте

HTML и формы · Junior

Зачем у поля формы нужен <label>? Как связать подпись с полем и почему placeholder её не заменяет?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как подписать группу переключателей radio?
  • Что делать, если по дизайну подписи нет — только иконка лупы в поиске?

Разбор

Два способа связи: <label for="email">Почта</label> <input id="email"> или поле внутри подписи: <label>Почта <input></label>. Значение for должно совпадать с id поля, а id — быть уникальным на странице.

Что это даёт: скринридер объявляет «Почта, поле ввода»; по подписи можно кликнуть, и это особенно важно для маленьких чекбоксов и переключателей на телефоне; голосовое управление находит поле по названию.

placeholder — пример или подсказка внутри поля. Он исчезает, как только пользователь начинает печатать, и человек забывает, что нужно было ввести. Обычно он ещё и бледный и плохо читается. Поэтому подпись должна быть постоянной и видимой, а placeholder — необязательным дополнением.

Уточняющие вопросы

Как подписать группу переключателей radio?

У каждого переключателя свой <label>, а у всей группы — общий вопрос в <fieldset> с <legend>. Тогда скринридер прочитает и вопрос, и вариант.

Что делать, если по дизайну подписи нет — только иконка лупы в поиске?

Дать полю невидимое имя: label, скрытый визуально классом sr-only, или атрибут aria-label="Поиск". Но видимая подпись удобнее для всех, поэтому её отсутствие стоит обсудить с дизайнером.

Что прозвучало в вашем ответе
  • label даёт полю доступное имя для скринридера.
  • Клик по подписи переводит фокус или отмечает чекбокс.
  • Названы оба способа связи: for + id и вложенность.
  • Placeholder исчезает при вводе и не заменяет подпись.

Почитать на сайте

HTML и формы · Junior

Чем отличается отправка формы методом GET от POST? Какой выбрать для поиска по сайту, а какой — для регистрации?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему после отправки POST-формы браузер при обновлении страницы спрашивает «Отправить данные повторно?»
  • Какие поля не попадут в отправку?

Разбор

Форма отправляет пары «name=значение» по адресу из атрибута action методом из атрибута method. По умолчанию метод GET.

GET добавляет данные в URL: /search?q=кот&page=2. Плюсы: результат можно добавить в закладки, поделиться ссылкой, вернуться кнопкой «Назад». Минусы: данные видны в адресной строке, истории браузера и журналах сервера, а длина адреса ограничена. GET по смыслу только получает данные и не должен ничего менять.

POST передаёт данные в теле запроса. Он подходит для создания и изменения: регистрация, комментарий, заказ, загрузка файлов (вместе с enctype="multipart/form-data"). Важно: POST не шифрует данные — защищает их только HTTPS. Пароль в POST по HTTP так же уязвим, как в GET, но хотя бы не остаётся в адресе и истории.

Уточняющие вопросы

Почему после отправки POST-формы браузер при обновлении страницы спрашивает «Отправить данные повторно?»

Обновление повторяет последний запрос, а повтор POST может создать второй заказ или комментарий. Поэтому сервер после успешного POST обычно делает перенаправление на страницу результата, которая открывается обычным GET.

Какие поля не попадут в отправку?

Поля без атрибута name, отключённые disabled и неотмеченные чекбоксы и переключатели.

Что прозвучало в вашем ответе
  • GET передаёт данные в URL, POST — в теле запроса.
  • GET-адрес можно сохранить и передать, он остаётся в истории.
  • POST — для действий, которые меняют данные на сервере.
  • Для поиска выбран GET, для регистрации — POST.
  • Упомянуто, что защиту данных даёт HTTPS, а не метод.

Почитать на сайте

HTML и формы · Junior

Зачем указывать у поля type="email", type="tel" или type="number", если можно везде оставить type="text"?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Если у поля type="email" и required, нужна ли проверка на сервере?
  • Что делает inputmode?

Разбор

Что даёт правильный тип:

  • Клавиатура на телефоне: для email — с символом @, для tel — цифровая, для url — с «.com» и косой чертой.
  • Проверка: email и url проверяют формат при отправке, number — что введено число в пределах min и max.
  • Элементы управления: date открывает календарь, range — ползунок, color — палитру.
  • Автозаполнение: вместе с атрибутом autocomplete браузер подставляет сохранённые почту, телефон и адрес.

type="number" подходит для количества, но не для номера карты, телефона или индекса: у них могут быть ведущие нули, а стрелки увеличения и колесо мыши там только мешают. Для таких полей берут type="text" с inputmode="numeric" — цифровая клавиатура без поведения числа.

Уточняющие вопросы

Если у поля type="email" и required, нужна ли проверка на сервере?

Да. Браузерную проверку легко обойти: отключить в инструментах разработчика или отправить запрос в обход формы. Она помогает пользователю, а защищает данные только проверка на сервере.

Что делает inputmode?

Подсказывает, какую экранную клавиатуру показать, но не меняет тип и проверку поля. Например, inputmode="numeric" у текстового поля для кода из SMS.

Что прозвучало в вашем ответе
  • Тип меняет экранную клавиатуру на телефоне.
  • Тип включает встроенную проверку формата.
  • Названы специальные элементы управления: дата, ползунок и т. п.
  • number не подходит для телефона и номеров с ведущими нулями.
  • Проверка на сервере всё равно нужна.

Почитать на сайте

HTML и формы · Junior

Чем атрибут id отличается от class? Для чего нужны атрибуты data-*?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему не стоит стилизовать по id?
  • Можно ли хранить в data-атрибутах секретные данные?

Разбор

id должен быть уникальным в документе. Его используют там, где нужна ссылка на конкретный элемент: якорь <a href="#contacts">, связь <label for>, атрибуты aria-describedby, быстрый поиск getElementById. Дубли id ломают эти связи: браузер найдёт только первый элемент.

class — повторяемая метка: class="button button--primary". Стили обычно пишут по классам: у селектора по id слишком высокая специфичность, и его потом трудно переопределить.

data-* — пользовательские атрибуты: <button data-product-id="42">. В JavaScript они доступны через element.dataset.productId, в CSS — через селектор [data-state="open"]. Так данные для скрипта не смешиваются с классами для оформления.

Уточняющие вопросы

Почему не стоит стилизовать по id?

У селектора с id специфичность намного выше, чем у класса. Чтобы потом переопределить такой стиль, придётся писать ещё более сильный селектор или !important. Кроме того, стиль по id нельзя переиспользовать для второго такого же элемента.

Можно ли хранить в data-атрибутах секретные данные?

Нет: всё, что есть в разметке, видно любому пользователю в коде страницы. Там хранят только то, что нужно интерфейсу.

Что прозвучало в вашем ответе
  • id уникален на странице, class может повторяться.
  • У элемента может быть несколько классов.
  • Названы применения id: якорь, label for, ARIA.
  • data-* хранят данные для скриптов, доступны через dataset.

Почитать на сайте

HTML и формы · Middle и выше

Два внешних классических скрипта зависят друг от друга и обращаются к DOM. Чем различаются обычный script, async и defer? Какой вариант выбрать при размещении в head?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Когда уместен async?
  • Нужен ли defer у type="module"?

Разбор

Для этих условий подойдёт defer у обоих внешних классических скриптов. Они загружаются параллельно разбору HTML, затем выполняются в порядке документа. async удобен независимому скрипту, но не цепочке зависимостей. Обычный script блокирует дальнейший разбор на время загрузки и выполнения. У модульных скриптов другая модель зависимостей; без async они и так отложены, defer для них не нужен.

Уточняющие вопросы

Когда уместен async?

Для независимых скриптов, которым не важен порядок и готовый DOM: счётчики аналитики, реклама.

Нужен ли defer у type="module"?

Нет: модульные скрипты и так откладываются до разбора документа и выполняются по порядку.

Что прозвучало в вашем ответе
  • Обычный скрипт без атрибутов останавливает разбор HTML.
  • async выполняется по готовности, порядок зависимых файлов не гарантирован.
  • defer сохраняет порядок, выполняется после разбора HTML до DOMContentLoaded.

Почитать на сайте

Спецификация: WHATWG: загрузка скриптов

HTML и формы · Middle и выше

Карточка занимает половину окна на десктопе и всю ширину на телефоне. Как помочь браузеру выбрать подходящий файл изображения? Когда нужен picture?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем дескриптор w отличается от x?
  • Как отдать WebP с запасным JPEG?

Разбор

Передайте варианты в srcset и согласованный с макетом sizes, например «(max-width: 600px) 100vw, 50vw», если отступов нет. Браузер выбирает ресурс с учётом размера и плотности пикселей. Саму ширину задаёт CSS. picture нужен, например, для крупного плана на телефоне вместо общего кадра. Атрибуты width и height помогают заранее зарезервировать место; это отдельная задача от выбора файла.

Уточняющие вопросы

Чем дескриптор w отличается от x?

2x указывает плотность пикселей и подходит для картинок фиксированного размера, например логотипа. 800w указывает ширину файла и работает вместе с sizes для резиновых картинок.

Как отдать WebP с запасным JPEG?

Через <picture>: <source type="image/webp" srcset="…">, а внутри обычный <img> с JPEG. Браузер выберет первый поддерживаемый источник.

Что прозвучало в вашем ответе
  • srcset с w описывает реальные ширины файлов; sizes описывает ожидаемый размер изображения в макете.
  • Выбор учитывает плотность пикселей; sizes не задаёт CSS-ширину.
  • picture применим для другого кадрирования или формата; width и height резервируют пропорции.

Почитать на сайте

Спецификация: WHATWG: адаптивные изображения

HTML и формы · Middle и выше

Главная страница медленно показывает первый экран: крупная картинка-обложка появляется последней. Какими средствами HTML можно ускорить её появление и не замедлить остальное?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем preload отличается от prefetch?
  • Что такое preconnect?

Разбор

Браузер узнаёт о картинке, только когда доходит до неё в HTML или в CSS, и сам решает, что важнее. Ему можно помочь:

  • у обложки не должно быть loading="lazy": ленивая загрузка откладывает её до раскладки;
  • fetchpriority="high" поднимает приоритет загрузки картинки первого экрана;
  • если обложка задана фоном в CSS, браузер найдёт её поздно. <link rel="preload" as="image" href="…"> в <head> начнёт загрузку сразу;
  • srcset и sizes дают телефону файл поменьше;
  • width и height резервируют место, и страница не прыгает.

Чтобы не замедлить остальное: картинки ниже первого экрана — loading="lazy", скрипты — defer или type="module", шрифты — font-display: swap. preload нельзя ставить на всё подряд: всё «срочное» означает, что срочного нет.

Проверять результат стоит в Lighthouse или во вкладке Performance по метрике LCP — времени появления самого крупного элемента первого экрана.

Уточняющие вопросы

Чем preload отличается от prefetch?

preload — ресурс нужен для текущей страницы, загрузить сейчас. prefetch — пригодится на следующей странице, загрузить в свободное время.

Что такое preconnect?

Заранее установить соединение с другим доменом, например со шрифтовым сервисом или CDN: DNS, TCP и TLS пройдут, пока браузер ещё разбирает страницу.

Что прозвучало в вашем ответе
  • Обложку первого экрана не загружают лениво.
  • Названы fetchpriority="high" или preload.
  • Картинки ниже первого экрана — loading="lazy", скрипты — defer.
  • Результат проверяют по метрике LCP.

Почитать на сайте

Спецификация: HTML: атрибут fetchpriority

HTML и формы · Middle и выше

Нужно окно подтверждения удаления и выпадающее меню профиля. Когда хватит нативного <dialog>, когда — атрибута popover, а когда понадобится свой код?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем dialog.show() отличается от showModal()?
  • Зачем верхний слой, если есть z-index?

Разбор

<dialog> с showModal() выводится в верхний слой, делает остальную страницу недоступной (inert), переводит фокус внутрь и закрывается по Escape с событием close. Для подтверждения удаления это почти всё, что нужно; остаётся выбрать начальный фокус и вернуть фокус на кнопку после закрытия.

popover="auto" — атрибут для неблокирующих всплывающих элементов: меню, подсказок, панелей. Кнопка с popovertarget открывает его без JavaScript, браузер выводит его поверх страницы, закрывает по щелчку мимо и по Escape и закрывает другие auto-панели, кроме связанных вложенных. У popover="manual" автоматического закрытия по щелчку мимо и Escape нет.

Свой код всё ещё нужен, чтобы:

  • привязать панель к кнопке и не дать ей выйти за край экрана (anchor positioning поддерживается ещё не везде);
  • сделать навигацию стрелками по пунктам меню;
  • плавно анимировать появление и исчезновение;
  • поддержать браузеры без этих возможностей, если это требуется.

Уточняющие вопросы

Чем dialog.show() отличается от showModal()?

show() открывает неблокирующее окно на месте в документе: фон остаётся доступным, Escape по умолчанию не закрывает. showModal() — модальное окно в верхнем слое.

Зачем верхний слой, если есть z-index?

Элемент в верхнем слое выше всего остального независимо от контекстов наложения. Окно с z-index: 9999 внутри родителя с transform может оказаться под соседней секцией, а верхний слой этой проблемы не имеет.

Что прозвучало в вашем ответе
  • Подтверждение — модальный <dialog> через showModal().
  • Меню — popover, без блокировки страницы.
  • Названо, что дают элементы: верхний слой, Escape, закрытие по щелчку мимо или блокировка фона.
  • Назван хотя бы один случай, когда нужен свой код.

Почитать на сайте

Спецификация: HTML: атрибут popover

HTML и формы · Middle и выше

Что такое веб-компоненты? Из каких частей они состоят и чем пользовательский элемент <user-card> отличается от компонента React?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как стилизовать компонент снаружи, если Shadow DOM его изолирует?
  • Что с доступностью и формами у пользовательских элементов?

Разбор

Три части:

  • Custom Elements — класс, наследник HTMLElement, регистрируется через customElements.define('user-card', UserCard). Имя обязательно с дефисом. Методы connectedCallback, disconnectedCallback и attributeChangedCallback вызываются при подключении, отключении и смене атрибутов из списка observedAttributes;
  • Shadow DOM — отдельное поддерево с границей инкапсуляции, а не защита секретных данных: стили внутри не утекают наружу, а внешние селекторы не ломают компонент;
  • <template> и <slot> — заготовка разметки и места, куда попадёт содержимое, переданное снаружи.

Отличие от React: React описывает интерфейс как функцию от состояния и сам обновляет DOM. Веб-компонент — это просто элемент DOM: обновлять его при изменении данных нужно вручную или через библиотеку вроде Lit. Зато он не зависит от фреймворка и подходит для дизайн-систем, которые используют разные команды.

Уточняющие вопросы

Как стилизовать компонент снаружи, если Shadow DOM его изолирует?

Через CSS-переменные — они проходят внутрь, через ::part() для явно открытых частей и через стили содержимого слотов.

Что с доступностью и формами у пользовательских элементов?

Роль и поведение элемент не получает сам. Нужно взять нативные элементы внутри или настроить роль и состояние; для участия в форме есть ElementInternals.

Что прозвучало в вашем ответе
  • Названы пользовательские элементы и customElements.define.
  • Назван Shadow DOM и изоляция стилей.
  • Названы <template> или <slot>.
  • Отличие от React: нет встроенного обновления по состоянию, зато нет зависимости от фреймворка.

Спецификация: HTML: пользовательские элементы

HTML и формы · Middle и выше

Сайт выходит на русском, английском и арабском. Что нужно учесть в разметке, чтобы браузер, поисковики и скринридеры правильно работали с каждой версией?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Можно ли определять язык по IP и сразу перенаправлять?
  • Почему не стоит зашивать текст в картинки?

Разбор

Язык. <html lang="ru"> говорит скринридеру, каким голосом читать, браузеру — какие правила переносов и кавычек применять, поисковику — для кого страница. Вставку на другом языке размечают отдельно: <span lang="en">.

Направление. Для арабского и иврита — dir="rtl": текст и порядок элементов идут справа налево. Для текста неизвестного направления, например имени пользователя, — dir="auto" или <bdi>.

Связь версий. В <head> каждой версии — ссылки на остальные: <link rel="alternate" hreflang="en" href="…">. Поисковик покажет пользователю его версию.

Вёрстка. Логические свойства CSS (margin-inline-start, padding-inline, inset-inline-end) сами отражаются для RTL. Немецкий и русский переводы бывают на треть длиннее английского — кнопки фиксированной ширины ломаются. Даты, числа и валюты форматируют через Intl, а не склеивают строки.

Уточняющие вопросы

Можно ли определять язык по IP и сразу перенаправлять?

Лучше не делать этого принудительно: человек может быть в поездке или читать на другом языке. Предложить версию и запомнить выбор — удобнее.

Почему не стоит зашивать текст в картинки?

Его нельзя перевести, увеличить, найти поиском и прочитать скринридером без отдельного альтернативного текста на каждом языке.

Что прозвучало в вашем ответе
  • Назван lang у страницы и у вставок на другом языке.
  • Назван dir="rtl" для арабского.
  • Названа связь версий через hreflang.
  • Названы логические свойства CSS или запас места под длинный перевод.

Почитать на сайте

Спецификация: W3C: язык в HTML

HTML и формы · Junior

Файл blog/post.html ссылается на index.html в корне сайта и на картинку blog/img/cat.jpg. Как записать оба пути? Что учесть, если ссылка открывается в новой вкладке?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему путь /img/cat.jpg не работает, если открыть страницу двойным щелчком по файлу?
  • Чем ссылка отличается от кнопки?

Разбор

Путь бывает трёх видов:

  • абсолютный адрес с протоколом: https://example.com/index.html — для других сайтов;
  • от корня сайта, начинается с /: /blog/img/cat.jpg — одинаково работает с любой страницы, но только на сервере, а не при открытии файла с диска;
  • относительно документа: img/cat.jpg — в папке рядом, ../index.html — на уровень выше.

Для blog/post.html: <a href="../index.html"> и <img src="img/cat.jpg" alt="…">.

target="_blank" открывает новую вкладку. Это стоит делать редко и предупреждать текстом или значком: человек с экранным диктором может не понять, почему перестала работать кнопка «Назад». Современные браузеры для _blank сами ведут себя как с rel="noopener", но для старых его указывают явно: иначе открытая страница получит доступ к window.opener.

Уточняющие вопросы

Почему путь /img/cat.jpg не работает, если открыть страницу двойным щелчком по файлу?

Без сервера корнем считается корень диска, а не папка сайта. Нужен локальный сервер или относительные пути.

Чем ссылка отличается от кнопки?

Ссылка ведёт на другой адрес, кнопка выполняет действие на странице. Удаление, отправка, открытие окна — кнопка.

Что прозвучало в вашем ответе
  • Записан путь на уровень выше: ../index.html.
  • Записан путь в дочернюю папку: img/cat.jpg.
  • Объяснено отличие пути от корня / от относительного.
  • Названы target="_blank" и предупреждение пользователя; упомянуто, что noopener браузеры включают сами.

Почитать на сайте

Спецификация: HTML: ссылки

HTML и формы · Junior

Когда для данных нужна таблица, а когда нет? Как разметить таблицу расписания, чтобы скринридер называл, к какому дню и времени относится ячейка?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делать с широкой таблицей на телефоне?
  • Зачем colspan и rowspan?

Разбор

Если данные сравнивают по двум осям — расписание, цены по тарифам, результаты — это таблица. Колонки сайта, карточки товаров и формы раскладывают через CSS Grid и Flexbox: таблица для них заставляет скринридер объявлять «строка 2, столбец 3» там, где смысла нет.

HTML
<table>
  <caption>Расписание занятий</caption>
  <thead>
    <tr><th scope="col">Время</th><th scope="col">Пн</th><th scope="col">Ср</th></tr>
  </thead>
  <tbody>
    <tr><th scope="row">10:00</th><td>HTML</td><td>CSS</td></tr>
  </tbody>
</table>

<caption> даёт таблице имя, <th> отличает заголовок от данных, а scope говорит, к чему он относится. На ячейке «CSS» скринридер прочитает «10:00, Ср». Для сложных таблиц с объединёнными заголовками есть атрибут headers.

Уточняющие вопросы

Что делать с широкой таблицей на телефоне?

Обернуть её в блок с overflow-x: auto, чтобы прокручивалась таблица, а не вся страница. Иногда данные лучше показать карточками, но тогда это уже не таблица.

Зачем colspan и rowspan?

Объединить ячейку на несколько столбцов или строк, например «Перерыв» на все дни. С ними важно проверить, что заголовки по-прежнему правильно связаны с ячейками.

Что прозвучало в вашем ответе
  • Таблица — для данных по строкам и столбцам, не для раскладки.
  • Назван <caption>.
  • Названы <th> и scope.
  • Объяснено, как скринридер связывает ячейку с заголовками.

Почитать на сайте

Спецификация: HTML: таблицы

HTML и формы · Junior

Чем <strong> отличается от <b>, а <em> — от <i>? Если выглядят одинаково, какая разница?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Читают ли скринридеры <strong> иначе?
  • Можно ли вложить <strong> в <strong>?

Разбор

По умолчанию <strong> и <b> жирные, <em> и <i> курсивные, но назначение у них разное:

  • <strong> — важное: «Не выключайте компьютер во время обновления»;
  • <em> — ударение: «Я просил чай» и «Я просил чай» — разный смысл;
  • <b> — выделить без важности: ключевые слова в обзоре, имя товара в описании;
  • <i> — другой голос текста: термин, мысль героя, латинское название, слово на другом языке (вместе с lang).

Если нужно только оформление — например, жирная подпись в карточке, — правильнее задать font-weight в CSS, а не брать тег ради вида.

Уточняющие вопросы

Читают ли скринридеры <strong> иначе?

Большинство по умолчанию не меняет интонацию. Но смысл важен для поиска, переводчиков, режимов чтения и будущих инструментов, поэтому тег выбирают по смыслу.

Можно ли вложить <strong> в <strong>?

Да, это повышает степень важности. На практике так делают редко.

Что прозвучало в вашем ответе
  • <strong> — важность, <em> — ударение.
  • <b> и <i> — выделение без важности, приведены примеры.
  • Вид задаёт CSS, тег выбирают по смыслу.

Почитать на сайте

Спецификация: HTML: семантика текста

HTML и формы · Middle и выше

В форме оформления заказа люди путаются в полях и бросают покупку на телефоне. Какие атрибуты HTML помогут браузеру заполнить форму за пользователя и показать удобную клавиатуру?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему autocomplete="off" на поле пароля плохая идея?
  • Где пригодится autocomplete="off"?

Разбор

Автозаполнение. Браузер хранит имя, адрес, телефон и карты, но подставит их, только если понимает поле. autocomplete="given-name", "email", "tel", "street-address", "postal-code", "cc-number", "cc-exp" — стандартные значения. Для адреса доставки и оплаты разные — префиксы shipping и billing. Для пароля — "new-password" при регистрации и "current-password" при входе: так менеджер паролей предложит сгенерировать или подставить нужный.

Клавиатура. type="email" и type="tel" показывают нужную раскладку. Для кода из СМС — inputmode="numeric" и autocomplete="one-time-code": телефон предложит код из сообщения. enterkeyhint="next" или "done" меняет подпись клавиши.

Чего избегать. Разбивать телефон или карту на несколько полей, запрещать вставку, ставить autocomplete="off" на всё подряд и использовать type="number" для индекса и номера карты — у числового поля есть стрелки, и ведущие нули теряются.

Уточняющие вопросы

Почему autocomplete="off" на поле пароля плохая идея?

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

Где пригодится autocomplete="off"?

В полях, где подсказка мешает: поиск по таблице, ввод чужих данных оператором, одноразовые значения.

Что прозвучало в вашем ответе
  • Назван autocomplete с конкретными значениями.
  • Названы type и inputmode для клавиатуры.
  • Назван one-time-code или пароли new-password/current-password.
  • Названа хотя бы одна ошибка: разбиение полей, запрет вставки, type="number".

Почитать на сайте

Спецификация: HTML: автозаполнение

HTML и формы · Middle и выше

Страница товара плохо выглядит в поиске и в мессенджерах: заголовок обрезан, описания нет, картинка случайная. Что поправить в разметке?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делает <meta name="robots" content="noindex">?
  • Помогает ли <meta name="keywords">?

Разбор

  • <title> — главный текст ссылки в выдаче: уникальный, с главным словом в начале, примерно до 60 символов, например «Рюкзак городской 20 л — Магазин».
  • <meta name="description"> — описание под ссылкой. Поисковик может заменить его своим отрывком, но хорошее описание часто показывают как есть.
  • <link rel="canonical"> — основной адрес, если страница открывается с фильтрами, метками рекламы или по разным путям. Иначе поисковик видит дубли.
  • Open Graph — <meta property="og:title">, og:description, og:image с картинкой нужного размера: мессенджеры и соцсети строят по ним карточку.
  • JSON-LD — <script type="application/ld+json"> с описанием по словарю schema.org (Product, цена, наличие): поисковик может показать их в сниппете.

Разметка не заменяет содержимое: важный текст должен быть на странице, а не только в метатегах, и страница должна быстро загружаться и работать без ошибок.

Уточняющие вопросы

Что делает <meta name="robots" content="noindex">?

Просит поисковики не добавлять страницу в выдачу. Ставят на служебные страницы, результаты внутреннего поиска, тестовые копии сайта.

Помогает ли <meta name="keywords">?

Нет, крупные поисковики давно его не учитывают.

Что прозвучало в вашем ответе
  • Названы уникальный <title> и description.
  • Назван canonical для дублей адресов.
  • Названы теги Open Graph для превью.
  • Названы структурированные данные JSON-LD.

Почитать на сайте

Спецификация: Протокол Open Graph

HTML и формы · Junior

Нужно встроить на страницу видео с YouTube и карту. Как это сделать через <iframe> и какие атрибуты нужны, чтобы фрейм был понятным, безопасным и не тормозил загрузку?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Можно ли ускорить страницу с несколькими видео ещё сильнее?
  • Почему адрес youtube-nocookie.com?

Разбор

HTML
<iframe
  src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
  title="Урок: первая HTML-страница"
  loading="lazy"
  allow="fullscreen; picture-in-picture"
  width="560" height="315">
</iframe>
  • title — имя фрейма для скринридера: «фрейм, Урок: первая HTML-страница». Без него непонятно, что внутри;
  • loading="lazy" — каждый фрейм загружает целую страницу со своими скриптами. Для фреймов ниже первого экрана это заметно ускоряет загрузку;
  • allow — список разрешённых возможностей: полноэкранный режим, камера, геолокация. Лишнего не давать;
  • sandbox — для недоверенного содержимого: запрещает скрипты, формы и переходы, пока их явно не разрешили;
  • адаптивность: в CSS width: 100%; height: auto; aspect-ratio: 16 / 9;.

Не каждый сайт можно встроить: многие запрещают это заголовками, тогда во фрейме будет ошибка.

Уточняющие вопросы

Можно ли ускорить страницу с несколькими видео ещё сильнее?

Показывать превью-картинку с кнопкой воспроизведения и подставлять <iframe> только после щелчка.

Почему адрес youtube-nocookie.com?

В этом режиме YouTube не ставит отслеживающие cookie, пока пользователь не запустит видео.

Что прозвучало в вашем ответе
  • Названы <iframe> и src.
  • Назван title для доступности.
  • Назван loading="lazy".
  • Названы allow, sandbox или адаптивный размер.

Почитать на сайте

Спецификация: HTML: элемент iframe

HTML и формы · Middle и выше

Встроенные подсказки браузера при проверке формы не совпадают с дизайном, а нужно ещё проверить, что пароли совпадают. Как сохранить браузерную проверку, но показать свои сообщения?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Когда показывать ошибку поля?
  • Почему не убрать атрибуты и проверять всё в JavaScript?

Разбор

JavaScript
form.noValidate = true
form.addEventListener('submit', (event) => {
  confirm.setCustomValidity(confirm.value === password.value ? '' : 'Пароли не совпадают')
  if (!form.checkValidity()) {
    event.preventDefault()
    showErrors(form)                       // свой вывод сообщений
    form.querySelector(':invalid').focus() // фокус к первой ошибке
  }
})

Что даёт API:

  • input.validity — флаги причин: valueMissing, typeMismatch, tooShort, patternMismatch, customError. По ним выбирают понятный текст: «Введите email в формате name@example.ru»;
  • input.validationMessage — стандартный текст браузера, если своего нет;
  • setCustomValidity(текст) помечает поле ошибочным, пустая строка снимает ошибку — её нужно сбрасывать при каждом вводе;
  • псевдоклассы :invalid и :user-invalid для оформления.

novalidate отключает только автоматическую остановку отправки и всплывающие подсказки — атрибуты и состояние полей продолжают работать. Сервер всё равно проверяет всё заново.

Уточняющие вопросы

Когда показывать ошибку поля?

При уходе из поля (blur) или при отправке, а исправленную ошибку убирать сразу при вводе.

Почему не убрать атрибуты и проверять всё в JavaScript?

Атрибуты дают правильную клавиатуру, состояние для скринридера и проверку, которая работает даже без скрипта.

Что прозвучало в вашем ответе
  • Атрибуты проверки остаются, подсказки отключает novalidate.
  • Названы validity или checkValidity().
  • Своё правило через setCustomValidity со сбросом.
  • Ошибка выводится текстом рядом с полем, сервер проверяет заново.

Почитать на сайте

Спецификация: HTML: проверка ограничений

HTML и формы · Middle и выше

Нужно нарисовать интерактивную схему зала с 300 местами и график с 50 000 точек. Что выбрать для каждого: SVG или <canvas>, и почему?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как ускорить canvas, если тяжёлый рендер подвешивает страницу?
  • Как сделать SVG-иконку доступной?

Разбор

SVG:

  • каждая фигура — узел DOM: на неё можно повесить обработчик, задать :hover, поставить фокус и aria-label;
  • чёткий при любом масштабе и на любом экране;
  • при тысячах элементов DOM становится тяжёлым, и перерисовка замедляется.

<canvas>:

  • одно полотно пикселей и API рисования (getContext('2d') или WebGL);
  • хорошо держит десятки тысяч объектов и анимацию — рисуется только картинка;
  • браузер не знает, где на полотне «место 12», — попадание мыши, фокус и состояние нужно считать самим;
  • для экранов высокой плотности полотно масштабируют на devicePixelRatio, иначе изображение размыто;
  • для скринридера нужна альтернатива: таблица данных или текстовое описание.

Схема зала — SVG: мест немного, каждое кликается и должно быть доступно с клавиатуры. График на 50 000 точек — canvas, а рядом — таблица или итоги текстом. Комбинировать тоже можно: данные на canvas, оси и подписи в SVG или HTML.

Уточняющие вопросы

Как ускорить canvas, если тяжёлый рендер подвешивает страницу?

Перерисовывать только изменившуюся часть, рисовать через requestAnimationFrame или вынести рисование в воркер через OffscreenCanvas.

Как сделать SVG-иконку доступной?

Декоративной — aria-hidden="true"; значимой — role="img" и aria-label или <title> внутри.

Что прозвучало в вашем ответе
  • SVG — элементы в DOM с событиями, CSS и доступностью.
  • Canvas — растровое полотно, хорошо для большого числа объектов.
  • Выбор обоснован для зала и графика.
  • Названа доступность canvas: таблица или описание.

Почитать на сайте

Спецификация: HTML: элемент canvas

HTML и формы · Middle и выше

Нужен блок «Вопросы и ответы», где открыт только один ответ. Можно ли обойтись <details> и <summary>? Что они дают и где ограничения?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Можно ли в <summary> поставить заголовок?
  • Что будет со старыми браузерами без поддержки name?

Разбор

HTML
<details name="faq" open>
  <summary>Как оплатить заказ?</summary>
  <p>Картой на сайте или при получении.</p>
</details>
<details name="faq">
  <summary>Сколько идёт доставка?</summary>
  <p>От одного до трёх дней.</p>
</details>

Что даёт браузер:

  • <summary> фокусируется и раскрывается Enter и пробелом, скринридер объявляет «свёрнуто/развёрнуто»;
  • атрибут open — состояние, событие toggle — изменение;
  • поиск по странице (Ctrl+F) находит текст в свёрнутом ответе и раскрывает его;
  • name объединяет блоки в группу «аккордеон» — открытие одного закрывает другие.

Ограничения: плавная анимация высоты требует дополнительных приёмов (::details-content, interpolate-size поддерживаются не везде); маркер оформляют через summary::marker или list-style: none со своим значком. Внутри <summary> не стоит размещать другие интерактивные элементы.

Свой виджет на <button aria-expanded> нужен, если требуется поведение, которого нет у <details>, например переход стрелками между заголовками.

Уточняющие вопросы

Можно ли в <summary> поставить заголовок?

Да, <summary><h3>…</h3></summary> — так вопросы попадут в навигацию по заголовкам. Но некоторые скринридеры читают такое сочетание по-разному, его стоит проверить.

Что будет со старыми браузерами без поддержки name?

Блоки будут открываться независимо. Это безопасный откат: содержимое остаётся доступным.

Что прозвучало в вашем ответе
  • Названы <details> и <summary> и что они дают без JavaScript.
  • Назван атрибут name для группы с одним открытым блоком.
  • Названо ограничение: анимация или оформление маркера.

Почитать на сайте

Спецификация: HTML: элемент details

HTML и формы · Junior

Кнопка с disabled="false" всё равно недоступна. Почему и как включать её из JavaScript?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что произойдёт при setAttribute("disabled", false)?
  • Как проверить текущее состояние флажка?

Разбор

disabled, checked и required — булевы атрибуты. Запись disabled="false" не означает ложь: атрибут присутствует, кнопка отключена. Используйте button.disabled = isBusy или button.toggleAttribute("disabled", isBusy).

Не переносите правило на все атрибуты: aria-expanded="false" — осмысленное состояние ARIA, а contenteditable имеет перечислимые значения.

Уточняющие вопросы

Что произойдёт при setAttribute("disabled", false)?

Значение преобразуется в строку, атрибут останется, кнопка будет отключена.

Как проверить текущее состояние флажка?

Через input.checked. Атрибут checked задаёт состояние по умолчанию, а пользователь меняет текущее свойство.

Что прозвучало в вашем ответе
  • Наличие disabled означает отключение независимо от строки.
  • Предложены свойство disabled или удаление атрибута.
  • Булевы HTML-атрибуты отличены от строковых состояний ARIA.

Почитать на сайте

Спецификация: Первоисточник: html

HTML и формы · Middle и выше

Кнопка в панели вызывает form.submit(), но проверка обязательных полей и обработчик submit не работают. Как отправить форму программно с обычным поведением?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как различить «Сохранить» и «Опубликовать» в одной форме?
  • Почему метод form.submit иногда перестаёт быть функцией?

Разбор

Обработчик ставят на форму, а не только на клик кнопки: форму отправляют и клавишей Enter. requestSubmit(saveButton) учитывает ограничения и параметры кнопки, например formaction. Если проверка не пройдена, события submit не будет; novalidate и formnovalidate намеренно отключают проверку.

В обработчике доступна event.submitter. Вызов submit() нужен только при осознанном обходе этих механизмов.

Уточняющие вопросы

Как различить «Сохранить» и «Опубликовать» в одной форме?

По event.submitter и значениям отправляющих кнопок. Для FormData можно передать кнопку вторым аргументом: new FormData(form, button).

Почему метод form.submit иногда перестаёт быть функцией?

Поле с name="submit" или id="submit" может перекрыть одноимённый метод формы. Лучше избегать такого имени.

Что прозвучало в вашем ответе
  • Объяснён обход проверки и события при submit().
  • Предложен requestSubmit() с учётом выбранной кнопки.
  • Обработчик отправки размещён на форме.

Почитать на сайте

Спецификация: Первоисточник: html

HTML и формы · Junior

Форма принимает аватар с accept="image/*". Достаточно ли этого, чтобы сервер получил только безопасные изображения, и как отправить файл через fetch?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему нельзя вручную поставить Content-Type: multipart/form-data?
  • Как убрать временный адрес предпросмотра?

Разбор

У поля нужен name: <input type="file" name="avatar" accept="image/*">. Отправка: fetch("/avatar", { method: "POST", body: new FormData(form) }). Браузер сам создаёт multipart/form-data с разделителем boundary.

На клиенте проверяют наличие файла и размер для быстрой обратной связи. Имя, расширение и file.type не доказывают содержимое. Сервер ограничивает размер, проверяет допустимый формат и обрабатывает файл, не доверяя присланному имени.

Уточняющие вопросы

Почему нельзя вручную поставить Content-Type: multipart/form-data?

Без согласованного boundary сервер не сможет надёжно разобрать части. Заголовок формирует браузер вместе с телом.

Как убрать временный адрес предпросмотра?

После использования адреса из URL.createObjectURL(file) вызывают URL.revokeObjectURL(url), когда картинка больше не нужна.

Что прозвучало в вашем ответе
  • accept назван подсказкой, а не проверкой содержимого.
  • Выбрана отправка FormData без ручного заголовка.
  • Разделены клиентская обратная связь и серверная проверка файла.

Почитать на сайте

Спецификация: Первоисточник: html

HTML и формы · Junior

На странице есть загрузка файла и показатель «занято 7 из 10 ГБ». Для чего подойдут <progress> и <meter>?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Когда нужен progress без value?
  • Как сообщить об окончании загрузки?

Разбор

Для загрузки: <progress id="upload" value="35" max="100">35%</progress> и связанный label. Если общий размер неизвестен, не придумывайте проценты: используйте progress без value.

Для диска: <meter id="disk" min="0" max="10" value="7">7 из 10 ГБ</meter>. Подпись объясняет единицы и смысл. Цвет шкалы не должен быть единственным способом понять состояние; текстовый результат остаётся рядом.

Уточняющие вопросы

Когда нужен progress без value?

Когда задача выполняется, но доля завершения неизвестна; value="0" означает известный нулевой прогресс.

Как сообщить об окончании загрузки?

Показать понятный результат и при необходимости обновить заранее существующую область role="status", не объявляя каждый процент.

Что прозвучало в вашем ответе
  • Выполнение задачи отличено от измерения в диапазоне.
  • Объяснено отсутствие value при неизвестном прогрессе.
  • Предусмотрены имя и текстовое состояние.

Почитать на сайте

Спецификация: Первоисточник: html

CSS и раскладка · Junior

Какова ширина border box в обоих случаях? Что произойдёт с margin?

                        .box {
  width: 200px;
  padding: 20px;
  border: 2px solid;
  margin: 10px;
  box-sizing: content-box; /* затем border-box */
}
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему часто пишут *, *::before, *::after { box-sizing: border-box; }?
  • Что такое схлопывание margin?

Разбор

При content-box к 200px содержимого добавляются 40px padding и 4px границы: получается 244px. При border-box заданные 200px уже включают padding и границы, содержимому остаётся 156px. Margin не входит в border box ни в одном варианте. Это объясняет, почему поле шириной 100% может вылезать из контейнера при добавлении внутренних отступов.

Уточняющие вопросы

Почему часто пишут *, *::before, *::after { box-sizing: border-box; }?

Так ширина включает внутренние отступы и рамку. Но если сумма padding и границ больше заданной ширины, содержимое сжимается только до нуля, а фактический блок будет шире: border-box не разрешает отрицательный размер содержимого.

Что такое схлопывание margin?

У соседних блоков в обычном потоке вертикальные отступы могут схлопываться. Для двух положительных берётся больший; для смешанных знаков складываются наибольший положительный и наиболее отрицательный. Между flex- и grid-элементами схлопывания нет.

Что прозвучало в вашем ответе
  • При content-box получается 244px, при border-box 200px.
  • Padding и border включаются в заданную ширину только при border-box.
  • Margin остаётся снаружи в обоих случаях.

Почитать на сайте

Спецификация: CSS Sizing: box-sizing

CSS и раскладка · Junior

Два правила задают кнопке разный цвет. Как браузер решает, какое применить? Что такое специфичность и как она считается?

                        #form .button { color: green; }
.form .button.primary { color: blue; }
button { color: red; }
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Влияет ли на специфичность селектор *, комбинаторы > и +?
  • Почему !important считают плохой практикой?
  • Что сильнее: :where() или :is()?

Разбор

Сначала каскад учитывает происхождение, важность и слои. Здесь сравниваем обычные авторские правила в одном слое, без анимаций, переходов и @scope.

Специфичность записывают как (a, b, c): a — число id, b — число классов, атрибутов и псевдоклассов, c — число тегов и псевдоэлементов. Сравнение идёт слева направо, как у разрядов числа.

  • #form .button — (1, 1, 0);
  • .form .button.primary — (0, 3, 0);
  • button — (0, 0, 1).

Побеждает первое правило: один id перевешивает любое количество классов. Если специфичность одинаковая, применяется правило, которое стоит позже в CSS. Inline-стиль в атрибуте style сильнее любых селекторов, а !important перекрывает обычные объявления.

На практике стараются держать специфичность низкой и ровной: писать стили по одному классу. Тогда порядок правил предсказуем, и не приходится «перебивать» стили ещё более длинными селекторами или !important.

Уточняющие вопросы

Влияет ли на специфичность селектор *, комбинаторы > и +?

Нет: универсальный селектор * и комбинаторы (пробел, >, +, ~) специфичность не добавляют. Учитываются только id, классы, атрибуты, псевдоклассы, теги и псевдоэлементы.

Почему !important считают плохой практикой?

!important участвует в каскаде, а не выключает его. Среди важных объявлений тоже учитываются происхождение и слои (их порядок обратный), затем специфичность. Частое применение затрудняет переопределения; сначала стоит разобраться в структуре стилей.

Что сильнее: :where() или :is()?

:is() берёт специфичность самого сильного селектора из списка, а :where() всегда даёт нулевую специфичность. Поэтому :where() удобен для базовых стилей, которые легко переопределить.

Что прозвучало в вашем ответе
  • Специфичность считается по группам: id, классы/атрибуты/псевдоклассы, теги.
  • Сравнение идёт по группам слева направо, один id сильнее любого числа классов.
  • При равной специфичности побеждает правило, написанное позже.
  • Упомянуты inline-стили и !important.
  • Для примера назван правильный результат — зелёный.

Почитать на сайте

CSS и раскладка · Junior

Почему color, заданный у <body>, применяется ко всему тексту, а border — нет? Что делают значения inherit и initial?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему ссылки не становятся цвета родителя?
  • Что сильнее: унаследованное значение или любое правило для элемента?

Разбор

Наследование избавляет от повторения: шрифт и цвет текста достаточно задать у body, и все потомки их получат. Свойства оформления блока не наследуются, иначе рамка у body появилась бы вокруг каждого абзаца и ссылки.

Управлять этим можно ключевыми словами, они работают для любого свойства:

  • inherit — взять значение родителя, даже если свойство не наследуется: border: inherit;
  • initial — начальное значение свойства по спецификации, например display: initial даёт inline, а не block;
  • unset — для наследуемых свойств работает как inherit, для остальных как initial.

Частая практика: button, input { font: inherit; }. У полей формы браузер задаёт свой шрифт, и без этого кнопки выглядят иначе, чем текст страницы.

Уточняющие вопросы

Почему ссылки не становятся цвета родителя?

У ссылок есть собственный цвет в стилях браузера, и правило для тега a сильнее унаследованного значения: наследование срабатывает, только если у элемента нет своего правила. Сделать ссылку цвета текста можно так: a { color: inherit; }.

Что сильнее: унаследованное значение или любое правило для элемента?

Любое правило, которое прямо подходит к элементу, даже * { color: black; }. Наследованное значение используется только тогда, когда своих правил нет.

Что прозвучало в вашем ответе
  • Наследуются в основном свойства текста.
  • Свойства блока (border, margin, padding, background) не наследуются.
  • inherit берёт значение родителя.
  • initial возвращает начальное значение по спецификации.
  • Прямое правило для элемента сильнее унаследованного значения.

Почитать на сайте

CSS и раскладка · Junior

Чем отличаются способы скрыть элемент: display: none, visibility: hidden и opacity: 0? Как скрыть текст визуально, но оставить для скринридера?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • В чём опасность opacity: 0 для скрытого меню?
  • Почему не скрыть текст через font-size: 0 или отрицательный text-indent?

Разбор

МестоКлик и фокусСкринридер
display: noneнетнетне читает
visibility: hiddenестьнетне читает
opacity: 0естьдачитает

Ещё есть атрибут hidden в HTML — то же, что display: none. Opacity и visibility можно плавно анимировать через transition, а display до недавнего времени — нет. Поэтому выпадающие меню часто прячут через visibility с opacity.

Для подписи, которую видит только скринридер (например, у кнопки-иконки), используют класс, который сжимает элемент до 1px и обрезает его:

CSS
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

Уточняющие вопросы

В чём опасность opacity: 0 для скрытого меню?

Прозрачные ссылки остаются на месте: их можно случайно нажать и до них доходит фокус клавиатуры. Пользователь нажимает Tab и «теряет» фокус в невидимом меню. Поэтому вместе с opacity задают visibility: hidden.

Почему не скрыть текст через font-size: 0 или отрицательный text-indent?

Такие приёмы ненадёжны: часть скринридеров пропускает текст нулевого размера, а элемент с огромным отрицательным отступом может выпасть за пределы экрана и вызвать прокрутку. Класс visually-hidden — проверенный вариант.

Что прозвучало в вашем ответе
  • display: none убирает элемент из раскладки.
  • visibility: hidden оставляет место.
  • opacity: 0 оставляет элемент кликабельным и доступным с клавиатуры.
  • Названо влияние на скринридеры.
  • Назван приём visually-hidden для текста только для скринридера.

Почитать на сайте

CSS и раскладка · Junior

Какие значения есть у position? Как сделать, чтобы значок «Новинка» был в правом верхнем углу карточки товара?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему position: sticky может не работать?
  • Как работает z-index?

Разбор

  • static — по умолчанию, свойства top/left не работают.
  • relative — элемент остаётся в потоке, место за ним сохраняется, а top/left сдвигают его визуально. Чаще всего relative нужен как точка отсчёта для вложенных absolute-элементов.
  • absolute — элемент вынимается из потока: соседи ведут себя так, будто его нет. Координаты отсчитываются от ближайшего позиционированного предка, а если такого нет — от начального блока страницы.
  • fixed — обычно координаты от окна. Но предок, например с transform, может стать содержащим блоком: тогда элемент привязан к нему. Шапки, кнопка «Наверх».
  • sticky — ведёт себя как обычный, пока родитель на экране, а при прокрутке прилипает на заданном top. Заголовки таблиц и боковые меню.
CSS
.card { position: relative; }
.card__badge { position: absolute; top: 8px; right: 8px; }

Без position: relative у карточки значок улетит в угол всей страницы или другого позиционированного предка.

Уточняющие вопросы

Почему position: sticky может не работать?

Частые причины: не задан top (или другое смещение), у одного из предков стоит overflow: hidden или auto, родитель по высоте не больше самого элемента — тогда липнуть некуда.

Как работает z-index?

z-index задаёт порядок наложения у позиционированных элементов, а также у flex- и grid-элементов. Но сравнивается он только внутри одного контекста наложения: элемент с z-index: 9999 не поднимется выше, если его родитель находится под соседним блоком.

Что прозвучало в вашем ответе
  • Названы значения static, relative, absolute, fixed, sticky.
  • absolute отсчитывается от ближайшего позиционированного предка.
  • absolute вынимает элемент из потока.
  • Для значка: родитель relative, значок absolute с top и right.
  • fixed — от окна, sticky — прилипает при прокрутке.

Почитать на сайте

CSS и раскладка · Junior

Как отцентрировать блок по горизонтали и по вертикали внутри родителя? Назовите несколько способов.

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему margin: 0 auto не центрирует по вертикали?
  • Чем justify-content отличается от align-items во flex?

Разбор

CSS
/* Flexbox */
.parent { display: flex; justify-content: center; align-items: center; }

/* Grid */
.parent { display: grid; place-items: center; }

/* Только по горизонтали, у блока есть ширина */
.child { width: 600px; margin: 0 auto; }

Важно, что по вертикали центрировать можно только внутри родителя, у которого есть высота. Если родитель по высоте равен содержимому, центрировать некуда. Для экрана целиком задают min-height: 100vh или 100dvh.

text-align: center центрирует строчное содержимое: текст, картинки, inline-block, но не сам блок. Устаревший приём для блоков неизвестного размера — position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%). Он работает, но вынимает элемент из потока, поэтому сегодня его используют только для наложений поверх содержимого.

Уточняющие вопросы

Почему margin: 0 auto не центрирует по вертикали?

В обычном блочном потоке auto у вертикальных отступов вычисляется как 0. Внутри flex- и grid-контейнера margin: auto работает в обе стороны и центрирует элемент.

Чем justify-content отличается от align-items во flex?

justify-content выравнивает элементы вдоль главной оси (по умолчанию — горизонталь), align-items — вдоль поперечной. При flex-direction: column оси меняются местами.

Что прозвучало в вашем ответе
  • Назван способ через flex: justify-content и align-items.
  • Назван способ через grid: place-items.
  • margin: 0 auto для горизонтального центрирования блока с шириной.
  • Упомянуто, что для вертикали у родителя нужна высота.

Почитать на сайте

CSS и раскладка · Junior

Когда для раскладки лучше выбрать Flexbox, а когда Grid?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делает gap?
  • Что означает flex: 1?

Разбор

Flexbox раскладывает элементы вдоль одной оси и распределяет между ними свободное место. Хорош, когда элементы разного размера и каждый занимает столько, сколько ему нужно: навигация, шапка с логотипом слева и кнопками справа, выравнивание иконки с текстом. С flex-wrap: wrap элементы переносятся, но строки не выравниваются друг с другом по колонкам.

Grid задаёт сетку на контейнере: grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)) — карточки одной ширины, которые сами переносятся, и колонки ровные во всех рядах. Каркас страницы «шапка, меню, содержимое, подвал» удобно описывать через grid-template-areas.

Они не конкурируют: часто страница собрана на Grid, а внутри карточки элементы выстроены Flexbox. Простое правило: если думаешь о строках и колонках вместе — Grid, если о ряде элементов — Flexbox.

Уточняющие вопросы

Что делает gap?

Задаёт расстояние между элементами во flex- и grid-контейнере, не добавляя отступ по краям. Раньше то же самое делали margin у элементов и отрицательным margin у контейнера.

Что означает flex: 1?

Это сокращение для flex-grow: 1, flex-shrink: 1, flex-basis: 0%. Элемент забирает свободное место наравне с другими элементами с flex: 1 и может сжиматься.

Что прозвучало в вашем ответе
  • Flexbox — одномерная раскладка, Grid — двумерная.
  • Во Flexbox размер элементов определяет содержимое, в Grid сетку задаёт контейнер.
  • Приведены уместные примеры для каждого.
  • Названо, что их совмещают на одной странице.

Почитать на сайте

CSS и раскладка · Junior

Чем отличаются единицы px, em, rem, % и vw? Что лучше использовать для размера шрифта?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем некоторые пишут html { font-size: 62.5%; }?
  • Что делает clamp() для шрифта?

Разбор

  • px — абсолютная единица. Не меняется от настроек шрифта браузера.
  • em — относительно font-size элемента. Во вложенных списках с font-size: 1.2em размер каждый раз умножается, что легко приводит к неожиданностям. Зато em удобен для отступов кнопки, которые должны расти вместе с её текстом.
  • rem — относительно font-size у html, по умолчанию 16px. 1.5rem = 24px везде, независимо от вложенности.
  • % — база зависит от свойства: для font-size это шрифт родителя, для размеров — соответствующий размер содержащего блока. У padding и margin в горизонтальном режиме письма база — ширина содержащего блока, даже для вертикальных отступов.
  • vw и vh — 1% ширины и высоты окна. На телефонах 100vh может быть больше видимой области из-за панели браузера, для этого есть dvh.

Шрифт в px не увеличится, если слабовидящий человек выставил в браузере крупный размер шрифта. rem уважает эту настройку, поэтому размеры шрифта задают в rem.

Уточняющие вопросы

Зачем некоторые пишут html { font-size: 62.5%; }?

Чтобы 1rem стал 10px и пересчитывать было проще. Но так легко забыть вернуть нормальный размер текста у body, а пересчитывать в уме не нужно — это делают DevTools или функция calc(). Сегодня этот приём используют редко.

Что делает clamp() для шрифта?

font-size: clamp(1rem, 2vw + 0.5rem, 2rem) — шрифт растёт вместе с шириной окна, но не меньше 1rem и не больше 2rem. Так делают «резиновые» заголовки без медиазапросов.

Что прозвучало в вашем ответе
  • px — фиксированная единица.
  • em — от шрифта элемента, накапливается при вложенности.
  • rem — от шрифта корневого элемента.
  • % — от содержащего блока (база зависит от свойства), vw/vh — от окна.
  • Для шрифта выбран rem, объяснено про пользовательские настройки.

Почитать на сайте

CSS и раскладка · Junior

Чем псевдоклассы отличаются от псевдоэлементов? Приведите примеры.

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему ::before не работает у img и input?
  • Чем :focus отличается от :focus-visible?
  • Можно ли размещать важный текст в content у ::before?

Разбор

Псевдоклассы — условия для выбора элемента:

  • состояние: :hover, :focus, :focus-visible, :active, :checked, :disabled, :invalid;
  • положение: :first-child, :last-child, :nth-child(2n);
  • логика: :not(), :is(), :has().

Псевдоэлементы — части элемента: ::before и ::after добавляют содержимое в начало и конец элемента (нужно свойство content, хотя бы пустое), ::first-letter, ::first-line, ::placeholder, ::selection, ::marker.

CSS
.link:hover { text-decoration: none; }
.required::after { content: " *"; color: red; }

Уточняющие вопросы

Почему ::before не работает у img и input?

::before и ::after вставляются внутрь элемента, перед его содержимым и после. У замещаемых элементов вроде img и input нет содержимого в этом смысле, поэтому у них псевдоэлементы не рисуются.

Чем :focus отличается от :focus-visible?

:focus срабатывает при любом фокусе, в том числе после клика мышью. :focus-visible — когда браузер считает, что рамку стоит показать, обычно при управлении с клавиатуры. Поэтому заметную обводку часто задают через :focus-visible, но не убирают совсем.

Можно ли размещать важный текст в content у ::before?

Лучше не надо: такой текст нельзя выделить и скопировать, а скринридеры читают его по-разному. Важное содержимое должно быть в HTML, а псевдоэлементы — для оформления.

Что прозвучало в вашем ответе
  • Псевдокласс выбирает элемент по состоянию или положению.
  • Псевдоэлемент — часть элемента, не существующая отдельным тегом.
  • Приведены примеры каждого.
  • Названо одно и два двоеточия.
  • Упомянуто свойство content для ::before и ::after.

Почитать на сайте

CSS и раскладка · Junior

Как сделать вёрстку адаптивной? Что такое медиазапросы и подход mobile first?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что ещё, кроме ширины, можно проверить в @media?
  • Что такое контейнерные запросы?

Разбор

Основа адаптивности — не медиазапросы, а гибкая раскладка: относительные ширины, max-width вместо width, flex и grid с переносом, картинки с max-width: 100%. Медиазапросы меняют то, что гибкостью не решить: число колонок, порядок блоков, вид меню.

CSS
.cards { display: grid; gap: 16px; }
@media (min-width: 768px) {
  .cards { grid-template-columns: 1fr 1fr; }
}

Mobile first: базовые стили — для телефона, а min-width-запросы добавляют возможности большим экранам. Так стили для узкого экрана проще, для широкого экрана добавляются только необходимые изменения. При этом общий CSS-файл загружается целиком: медиазапрос сам по себе не уменьшает его размер. Обратный подход — desktop first с max-width.

Точки перелома лучше выбирать там, где ломается конкретная вёрстка, а не по списку моделей телефонов. Обязательно нужен <meta name="viewport" content="width=device-width, initial-scale=1">, иначе телефон эмулирует ширину около 980px.

Уточняющие вопросы

Что ещё, кроме ширины, можно проверить в @media?

Ориентацию (orientation), тип указателя (hover: hover, pointer: coarse — сенсорный экран), настройки пользователя: prefers-color-scheme: dark и prefers-reduced-motion: reduce, печать (@media print).

Что такое контейнерные запросы?

@container проверяет размер не окна, а родительского контейнера. Карточка может перестраиваться в зависимости от того, в какой колонке она стоит, а не от ширины всего экрана.

Что прозвучало в вашем ответе
  • Названа гибкая раскладка: относительные размеры, flex, grid.
  • Объяснён медиазапрос с условием по ширине.
  • Mobile first: базовые стили для узкого экрана, min-width для широких.
  • Упомянут meta viewport.

Почитать на сайте

CSS и раскладка · Middle и выше

Почему у кнопки будет красный текст, хотя синий селектор содержит ID? Условия: один авторский stylesheet, нет inline-стилей, !important, анимаций и других правил.

                        @layer base {
  #app .button { color: blue; }
}
.button { color: red; }
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем вообще нужны каскадные слои?
  • Как меняется порядок для !important?

Разбор

Правило .button находится вне слоя и выигрывает у обычного правила внутри base. До сравнения специфичности дело не доходит. Поэтому добавлять ID для исправления такого конфликта бессмысленно: сначала нужно понять структуру каскада. Для !important порядок слоёв меняется, но в условиях вопроса таких объявлений нет.

Уточняющие вопросы

Зачем вообще нужны каскадные слои?

Чтобы управлять приоритетом групп стилей — сброс, библиотека, компоненты, утилиты — порядком слоёв, а не наращиванием специфичности.

Как меняется порядок для !important?

Для важных объявлений он переворачивается: !important в более раннем слое сильнее, чем в позднем и вне слоёв.

Что прозвучало в вашем ответе
  • Обычное правило вне слоя сильнее обычного правила внутри слоя.
  • Специфичность сравнивается после происхождения, важности и слоя.
  • Порядок классов в class не решает конфликт.

Почитать на сайте

Спецификация: CSS Cascade: порядок каскада

CSS и раскладка · Middle и выше

В строке Flexbox есть иконка и длинный заголовок. Заголовок имеет flex: 1 и white-space: nowrap, но не сжимается. Исправьте CSS заголовка, чтобы он сжимался и показывал многоточие. Запишите код и объясните изменения.

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему не помогает overflow: hidden у всего контейнера?
  • Как обрезать текст не в одну строку, а в три?

Разбор

flex: 1 не отменяет автоматический минимальный размер. Неразрывная строка может удерживать элемент шире доступного места. Установите min-width: 0 на flex-элемент с заголовком, затем overflow: hidden и text-overflow: ellipsis; nowrap уже задан. Если текст находится во вложенном элементе, проверьте ограничения и его flex-предков. Скрытие переполнения всего контейнера лишь обрежет интерфейс и не объяснит причину.

Уточняющие вопросы

Почему не помогает overflow: hidden у всего контейнера?

Он просто обрежет вылезающую часть, включая кнопки справа. Причина — в минимальном размере flex-элемента, и исправлять нужно его.

Как обрезать текст не в одну строку, а в три?

Для совместимого варианта используют display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;. Поддержку непредварённого line-clamp проверяют отдельно для целевых браузеров. Полный текст должен оставаться доступен, если он нужен для действия.

Что прозвучало в вашем ответе
  • У flex-элемента может срабатывать автоматический минимум по содержимому.
  • min-width: 0 задаётся сжимаемому элементу.
  • Для многоточия нужны overflow: hidden, text-overflow: ellipsis и ограниченная ширина.

Почитать на сайте

Спецификация: CSS Flexbox: минимальный размер

CSS и раскладка · Middle и выше

Сетка с колонками 240px 1fr выходит за ширину экрана из-за длинной строки кода во второй колонке. Запишите исправленные CSS-правила для сетки и блока кода. Объясните, почему 1fr не спасает.

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сделать, чтобы на телефоне колонки встали друг под другом?
  • Чем это похоже на проблему с flex?

Разбор

Одиночный 1fr подразумевает автоматический минимум дорожки, поэтому длинное содержимое может расширить её. minmax(0, 1fr) разрешает дорожке стать уже. На элементе с кодом задайте подходящую обработку переполнения, например overflow: auto. Нулевая нижняя граница не переносит текст сама. Также нужна адаптация фиксированной колонки 240px, если весь макет должен работать на очень узком экране.

Уточняющие вопросы

Как сделать, чтобы на телефоне колонки встали друг под другом?

Медиазапросом сменить шаблон на одну колонку или сразу задать repeat(auto-fit, minmax(240px, 1fr)).

Чем это похоже на проблему с flex?

Механизм тот же: автоматический минимальный размер по содержимому. Во flex его снимает min-width: 0, в grid — minmax(0, 1fr).

Что прозвучало в вашем ответе
  • 1fr делит свободное место, но автоматический минимум может зависеть от содержимого.
  • Предложен minmax(0, 1fr) для второй дорожки.
  • Переполнение самого кода обрабатывается отдельно, например прокруткой.

Почитать на сайте

Спецификация: CSS Grid: размеры дорожек

CSS и раскладка · Middle и выше

Всплывающая панель с z-index: 9999 всё равно находится под соседней секцией с z-index: 2. Родитель панели имеет position: relative и z-index: 1. Как объяснить и исправить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что ещё создаёт контекст наложения?
  • Как вывести модальное окно поверх всего без z-index?

Разбор

Потомок не может своим z-index перепрыгнуть соседний более высокий контекст родителя. Сначала проверьте контексты в DevTools. Можно изменить уровень родителя, убрать ненужный контекст или разместить панель в подходящем контейнере. Для настоящего диалога или popover рассмотрите нативный верхний слой. Простое увеличение 9999 не решает ограничение родительского контекста.

Уточняющие вопросы

Что ещё создаёт контекст наложения?

opacity меньше 1, transform, filter, will-change, position: fixed или sticky, isolation: isolate, flex- и grid-элементы с z-index.

Как вывести модальное окно поверх всего без z-index?

Нативный <dialog> с showModal() или атрибут popover выводят элемент в верхний слой (top layer), выше любых z-index.

Что прозвучало в вашем ответе
  • Родитель создаёт контекст наложения, внутри которого ограничен z-index потомка.
  • Сравниваются контексты родителей, а не все числа в документе.
  • Предложено изменить структуру/уровень родителя или использовать подходящий верхний слой.

Почитать на сайте

Спецификация: CSS Position: контексты наложения

CSS и раскладка · Middle и выше

Как работают пользовательские свойства CSS (--brand: #0a6)? Чем они отличаются от переменных препроцессора и как сделать тёмную тему, не переписывая все правила?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что будет, если значение свойства некорректно для того места, где его подставили?
  • Зачем @property?

Разбор

Свойство с двумя дефисами объявляют как обычное: :root { --text: #222; --page: #fff; }, а используют через var(--text). Второй аргумент — запасное значение: var(--gap, 16px).

Главное отличие от переменных Sass — значение вычисляется в браузере для каждого элемента:

  • переопределение действует на элемент и всех его потомков: .card--danger { --accent: red; };
  • его можно менять в медиазапросе и из JavaScript: el.style.setProperty('--x', '10px');
  • оно проходит внутрь Shadow DOM.

Тёмная тема — это смена значений, а не правил:

CSS
:root { --text: #222; --page: #fff; }
@media (prefers-color-scheme: dark) {
  :root { --text: #eee; --page: #1b1d21; }
}
body { color: var(--text); background: var(--page); }

Ручной переключатель темы добавляет атрибут, например [data-theme="dark"], с теми же переопределениями.

Уточняющие вопросы

Что будет, если значение свойства некорректно для того места, где его подставили?

Объявление не отбрасывается при разборе, а становится недействительным во время вычисления: свойство получает унаследованное или начальное значение, а не значение из предыдущего правила.

Зачем @property?

Чтобы задать свойству тип, начальное значение и наследование. Типизированное свойство можно плавно анимировать, например угол градиента.

Что прозвучало в вашем ответе
  • Объявление через --имя и использование через var(), запасное значение.
  • Свойства наследуются и переопределяются для части страницы.
  • Отличие от препроцессора: значение вычисляется в браузере.
  • Тёмная тема — переопределение значений в медиазапросе или по атрибуту.

Спецификация: CSS Custom Properties

CSS и раскладка · Middle и выше

В проекте стили кнопки переопределяют в пяти местах, а в конце появляется !important. Как организовать CSS, чтобы такого не было? Как переопределять стили компонента UI-библиотеки?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Когда !important всё-таки уместен?
  • Чем CSS Modules отличаются от БЭМ?

Разбор

Войны специфичности начинаются, когда правило написано «по месту»: .sidebar .form .button. Следующему разработчику, чтобы его перебить, нужен селектор ещё сильнее, и в конце — !important.

Что помогает:

  • Плоские селекторы. Один класс на правило, одинаковая специфичность у всех компонентов. :where() обнуляет специфичность там, где нужна сложная выборка;
  • Именование по компоненту. БЭМ (.card, .card__title, .card--wide) или CSS Modules: стиль принадлежит компоненту, а не месту на странице;
  • Варианты — модификаторами. Не «кнопка в шапке синяя», а .button--primary, который ставят там, где нужно;
  • Слои. @layer reset, base, components, utilities; — порядок слоёв важнее специфичности, поэтому обычное объявление утилиты сильнее обычного объявления компонента при прочих равных; для !important порядок слоёв обратный.

Стили UI-библиотеки: сначала посмотреть, нет ли у неё переменных или параметров темы. Если нет — подключить её в нижнем слое @layer vendor, тогда любые свои правила в слоях выше её перебьют без повышения специфичности.

Уточняющие вопросы

Когда !important всё-таки уместен?

В утилитах, которые обязаны сработать, например .visually-hidden, и в пользовательских стилях. В обычных компонентах это сигнал, что структура стилей сломана.

Чем CSS Modules отличаются от БЭМ?

БЭМ — договорённость об именах, которую соблюдают люди. CSS Modules делают имена уникальными при сборке, поэтому случайно задеть чужой класс нельзя.

Что прозвучало в вашем ответе
  • Названа причина: рост специфичности из-за правил «по месту».
  • Плоские селекторы с одинаковой специфичностью.
  • Названа методология именования (БЭМ, CSS Modules) или модификаторы вариантов.
  • Названы @layer или переменные библиотеки для переопределения.

Почитать на сайте

Спецификация: CSS Cascade 5: слои

CSS и раскладка · Middle и выше

Карточка товара стоит и в широкой сетке, и в узкой боковой колонке. Почему медиазапросы здесь плохо подходят и как решают задачу контейнерные запросы?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему нельзя сделать контейнером саму карточку и менять её же раскладку?
  • Что значит container-type: inline-size?

Разбор

На широком экране карточка в боковой колонке может быть всего 280px шириной. Медиазапрос @media (min-width: 1024px) сработает и включит горизонтальную раскладку, для которой места нет.

CSS
.card-slot { container-type: inline-size; }

.card { display: grid; gap: 12px; }
@container (min-width: 400px) {
  .card { grid-template-columns: 160px 1fr; }
}

Обёртка становится контейнером запросов, и правила внутри @container проверяют её ширину. Контейнеру можно дать имя (container-name), чтобы запрашивать конкретного предка. Есть и единицы контейнера: cqi — процент ширины контейнера, удобно для размера шрифта заголовка.

Медиазапросы остаются для раскладки страницы целиком, а контейнерные — для компонентов, которые переиспользуются в разных местах.

Уточняющие вопросы

Почему нельзя сделать контейнером саму карточку и менять её же раскладку?

Запрос проверяет контейнер, а стили применяются к его потомкам. Если бы элемент менял свой размер в зависимости от своего же размера, получился бы бесконечный цикл. Поэтому нужна обёртка.

Что значит container-type: inline-size?

Контейнер отслеживает ширину в направлении строки и перестаёт зависеть по этой оси от размеров содержимого. Ширину ему задаёт окружение — сетка или колонка.

Что прозвучало в вашем ответе
  • Медиазапрос зависит от окна, а не от места компонента.
  • Названы container-type у родителя и @container.
  • Стили применяются к потомкам контейнера.
  • Разделение ролей: страница — медиазапросы, компонент — контейнерные.

Почитать на сайте

Спецификация: CSS Containment 3: контейнерные запросы

CSS и раскладка · Middle и выше

Когда анимацию лучше сделать на CSS, а когда на JavaScript? Как сделать её плавной и не навредить людям, которым движение мешает?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему анимация «дёргается» на слабом телефоне?
  • Как дождаться конца CSS-перехода в коде?

Разбор

CSS (transition, @keyframes) хорош, когда есть начальное и конечное состояние: наведение, раскрытие, появление. Анимация описана декларативно, может работать в потоке композиции и не мешает основному потоку.

JavaScript нужен, когда параметры вычисляются: элемент следует за пальцем, анимация останавливается на середине, шаги зависят от ответа сервера. element.animate() (Web Animations API) даёт те же возможности, что CSS, плюс управление из кода: пауза, обратный ход, промис окончания. Ручные анимации в цикле — только через requestAnimationFrame.

Плавность. transform и opacity не требуют раскладки; top, width, height — требуют на каждом кадре. will-change — подсказка заранее, но не для всех элементов подряд.

Доступность. У части людей движение вызывает головокружение. В медиазапросе prefers-reduced-motion: reduce крупное движение убирают или заменяют плавным появлением.

Уточняющие вопросы

Почему анимация «дёргается» на слабом телефоне?

Обычно из-за раскладки на каждом кадре или тяжёлой отрисовки (тени, размытие) либо из-за загруженного основного потока скриптами. Вкладка Performance показывает, на что уходит время кадра.

Как дождаться конца CSS-перехода в коде?

Событие transitionend или animationend, а у Web Animations API — animation.finished.

Что прозвучало в вашем ответе
  • CSS — для переходов между состояниями, JS — для движения по логике.
  • Названы transform и opacity для плавности.
  • Назван requestAnimationFrame или Web Animations API.
  • Назван prefers-reduced-motion.

Почитать на сайте

Спецификация: Web Animations

CSS и раскладка · Junior

Чем margin отличается от padding? У абзацев margin: 20px 0, но между ними 20px, а не 40px. Почему?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему заголовок внутри карточки сдвигает вниз всю карточку?
  • Можно ли задать отрицательный margin?

Разбор

В блочной модели снаружи внутрь: margin → border → padding → содержимое. Фон элемента виден под содержимым и padding, но не под margin. Поэтому кнопке, чтобы стать больше, добавляют padding, а чтобы отодвинуть её от текста — margin.

Схлопывание (margin collapsing) бывает только у вертикальных отступов обычных блоков:

  • у соседей: нижний отступ одного и верхний другого сливаются в один, равный большему, если оба положительные;
  • у родителя и первого или последнего ребёнка, если между ними нет padding, border и содержимого: отступ ребёнка «выпадает» наружу родителя.

Схлопывания нет внутри Flexbox и Grid, у горизонтальных отступов между контейнером с display: flow-root и его детьми. Внешние отступы такого контейнера всё ещё могут схлопываться с соседними блоками. Многие задают отступы только в одну сторону, например margin-bottom, или gap во Flexbox и Grid — так поведение предсказуемее.

Уточняющие вопросы

Почему заголовок внутри карточки сдвигает вниз всю карточку?

Его margin-top схлопнулся с отступом карточки и оказался снаружи. Помогает padding у карточки, display: flow-root или margin-top: 0 у первого ребёнка.

Можно ли задать отрицательный margin?

Да: элемент сдвигается и может наехать на соседа. Отрицательного padding не бывает.

Что прозвучало в вашем ответе
  • padding внутри рамки и закрашивается фоном, margin снаружи.
  • Вертикальные отступы соседей схлопываются до большего.
  • Названо, где схлопывания нет: Flexbox, Grid, горизонтальные отступы или flow-root.

Почитать на сайте

Спецификация: CSS 2: схлопывание отступов

CSS и раскладка · Junior

Как подключить на сайт свой шрифт? Почему при загрузке текст сначала невидим или «прыгает», и как это смягчить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем список шрифтов через запятую в font-family?
  • Что такое вариативный шрифт?

Разбор

CSS
@font-face {
  font-family: 'Golos';
  src: url('/fonts/golos.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
body { font-family: 'Golos', Arial, sans-serif; }

У статического шрифта каждое начертание — обычное, жирное, курсив — обычно отдельный файл и отдельное правило @font-face с font-weight и font-style. Иначе браузер сам «нарисует» жирный, и выйдет некрасиво.

Пока шрифт не загружен:

  • при font-display: auto период скрытия текста определяется браузером; фиксированное время не гарантируется;
  • font-display: swap сразу показывает запасной шрифт и меняет, когда загрузится свой;
  • смена шрифта двигает строки — это сдвиг раскладки. Его уменьшают похожим запасным шрифтом и настройкой size-adjust.

Ускорить: формат WOFF2, только нужные начертания и символы (кириллица и латиница), <link rel="preload" as="font" type="font/woff2" crossorigin> для главного шрифта.

Уточняющие вопросы

Зачем список шрифтов через запятую в font-family?

Если первого нет или он не загрузился, браузер берёт следующий. В конце ставят общее семейство — sans-serif или serif.

Что такое вариативный шрифт?

Один файл, в котором насыщенность и другие параметры меняются плавно. Вместо пяти файлов начертаний — один, иногда меньшего общего размера.

Что прозвучало в вашем ответе
  • Назван @font-face с файлом WOFF2.
  • Назван список запасных шрифтов в font-family.
  • Назван font-display: swap и проблема невидимого текста.
  • Названы способы ускорить: preload, только нужные начертания.

Почитать на сайте

Спецификация: CSS Fonts 4: @font-face

CSS и раскладка · Junior

Зачем в начале стилей сайта пишут сброс стилей? Чем полный сброс отличается от normalize и что обычно входит в современный минимальный набор?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему поля формы не наследуют шрифт страницы?
  • Что делает box-sizing: border-box?

Разбор

У каждого браузера есть свои стили по умолчанию: body с отступом 8px, списки со своими отступами, кнопки и поля со своим шрифтом. Чтобы сайт выглядел одинаково, это выравнивают в начале CSS.

  • Полный сброс (reset) убирает почти всё: заголовки одного размера, у списков нет маркеров. Плюс — полный контроль, минус — приходится заново оформлять даже то, что было удобно.
  • Normalize оставляет разумные значения по умолчанию и только исправляет различия между браузерами.
CSS
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; line-height: 1.5; }
img, svg, video { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; }

Сброс не должен ломать доступность: нельзя убирать обводку фокуса без замены и маркеры у списков, которые остаются списками по смыслу, без проверки, что скринридер их всё ещё понимает.

Уточняющие вопросы

Почему поля формы не наследуют шрифт страницы?

У них свои системные стили. font: inherit заставляет их брать шрифт родителя.

Что делает box-sizing: border-box?

Заданная ширина включает padding и рамку. Блок в 300px остаётся 300px после добавления внутренних отступов.

Что прозвучало в вашем ответе
  • Названа причина: разные стили браузеров по умолчанию.
  • Объяснено отличие полного сброса от normalize.
  • Названы хотя бы два правила минимального набора.
  • Сброс не убирает видимый фокус.

Почитать на сайте

Спецификация: CSS Cascade: стили браузера

CSS и раскладка · Junior

Во Flexbox justify-content: center центрирует элементы по горизонтали, а после flex-direction: column — по вертикали. Почему? Чем justify-content отличается от align-items?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем align-content?
  • Как прижать одну кнопку к правому краю, а остальные оставить слева?

Разбор

Ниже предполагается обычное горизонтальное направление письма writing-mode: horizontal-tb.

  • flex-direction: row (по умолчанию) — главная ось горизонтальная, поперечная — вертикальная;
  • flex-direction: column — главная вертикальная, поперечная — горизонтальная.

justify-content (главная ось): flex-start, center, space-between — первый и последний к краям, остальное поровну, space-around, space-evenly.

align-items (поперечная ось): stretch — растянуть на всю высоту строки (по умолчанию), center, flex-start, baseline — по линии текста. align-self задаёт то же для одного элемента.

CSS
.header { display: flex; justify-content: space-between; align-items: center; }

Логотип слева, меню справа, оба по центру высоты шапки. Отступы между элементами удобнее задавать gap, а не margin.

Уточняющие вопросы

Зачем align-content?

align-content распределяет строки многострочного flex-контейнера при свободном месте по поперечной оси. При flex-wrap: nowrap не действует. Контейнер с wrap считается многострочным даже тогда, когда элементы пока поместились в одну строку.

Как прижать одну кнопку к правому краю, а остальные оставить слева?

Дать ей margin-left: auto: автоматический отступ забирает всё свободное место.

Что прозвучало в вашем ответе
  • Названы главная и поперечная оси.
  • justify-content — главная ось, align-items — поперечная.
  • flex-direction: column меняет главную ось.

Почитать на сайте

Спецификация: CSS Flexbox: выравнивание

CSS и раскладка · Junior

Нужна сетка карточек: на широком экране четыре в ряд, на узком — сколько поместится, но не уже 220px, и без медиазапросов. Как это сделать на Grid?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сделать, чтобы одна карточка занимала две колонки?
  • Что такое grid-template-areas?

Разбор

CSS
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
  max-width: calc(4 * 220px + 3 * 16px);
}

Максимальная ширина здесь 928px: четыре минимальные колонки по 220px и три промежутка по 16px. Пятой колонке нужно уже 1164px, поэтому она не появится.

  • fr — доля свободного места: колонки делят остаток поровну;
  • minmax(220px, 1fr) — колонка не уже 220px и не шире доли;
  • repeat(auto-fill, …) — столько колонок, сколько влезет;
  • auto-fit отличается, когда карточек мало: пустые колонки схлопываются, и карточки растягиваются на всю ширину. auto-fill оставляет пустые места;
  • gap — отступы между ячейками без лишних отступов по краям.

Если карточка уже 220px на очень маленьком экране не помещается, вместо 220px пишут min(220px, 100%).

Уточняющие вопросы

Как сделать, чтобы одна карточка занимала две колонки?

grid-column: span 2. С автоматическими колонками на узком экране она может не поместиться, поэтому иногда это включают медиазапросом.

Что такое grid-template-areas?

Схема раскладки именованными областями, например "header header" "sidebar main". Удобно для каркаса страницы.

Что прозвучало в вашем ответе
  • Назван repeat(auto-fill или auto-fit, minmax(220px, 1fr)).
  • Объяснены fr и minmax.
  • Назван gap.

Почитать на сайте

Спецификация: CSS Grid: repeat()

CSS и раскладка · Middle и выше

Нужно подсветить карточку, в которой отмечен флажок, и форму, в которой есть поле с ошибкой, — без JavaScript. Как это сделать через :has() и чем он отличается от :is() и :where()?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем :user-invalid лучше :invalid?
  • Можно ли вкладывать :has() в :has()?

Разбор

CSS
.card:has(input:checked) { border-color: var(--accent); }
form:has(:user-invalid) .submit { opacity: 0.6; }
.field:has(+ .hint) { margin-bottom: 4px; } /* поле, за которым идёт подсказка */

Раньше CSS умел выбирать только вниз и вправо по дереву. :has() — «родительский селектор»: условие проверяется внутри элемента (или у соседей через + и ~), а стиль применяется к нему самому.

  • :is(h1, h2, h3) a = три селектора сразу. Специфичность — как у самого сильного аргумента;
  • :where(h1, h2, h3) a — то же, но специфичность аргументов нулевая: такие правила легко перебить, удобно для базовых стилей и сброса;
  • оба «прощающие»: неизвестный браузеру аргумент не ломает весь список.

:has() внутри себя не прощающий: если там нераспознанный селектор, правило отбрасывается. Тяжёлые условия на больших деревьях, например body:has(…) для всего подряд, могут замедлять пересчёт стилей.

Уточняющие вопросы

Чем :user-invalid лучше :invalid?

:invalid срабатывает сразу при загрузке, и пустое обязательное поле краснеет до ввода. :user-invalid — только после того, как человек взаимодействовал с полем.

Можно ли вкладывать :has() в :has()?

Нет, вложенный :has() запрещён.

Что прозвучало в вашем ответе
  • :has() выбирает элемент по содержимому или соседям.
  • Записаны селекторы для карточки и формы.
  • Объяснено отличие :is() и :where() по специфичности.

Почитать на сайте

Спецификация: Selectors 4: :has()

CSS и раскладка · Middle и выше

Заголовок должен быть 24px на телефоне и 48px на широком экране, плавно меняясь между ними, без пачки медиазапросов. Как это сделать и что учесть для доступности?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем clamp() отличается от min() и max()?
  • Почему максимальный размер стоит ограничивать?

Разбор

clamp(min, preferred, max) возвращает предпочтительное значение, ограниченное с двух сторон. Для плавной типографики предпочтительное — смесь фиксированной части и доли ширины окна:

CSS
h1 { font-size: clamp(1.5rem, 1rem + 2.5vw, 3rem); }

Как посчитать середину: при ширине 320px нужно 24px, при 1280px — 48px. Наклон = (48 − 24) / (1280 − 320) = 0.025, то есть 2.5vw; начальная часть = 24 − 320 × 0.025 = 16px = 1rem при корневом размере шрифта 16px.

Доступность: если написать clamp(24px, 5vw, 48px), при увеличении текста в настройках браузера размер почти не изменится, а при масштабе страницы vw тоже не растёт. Часть в rem сохраняет реакцию на настройки пользователя. Тот же приём подходит для отступов: padding: clamp(1rem, 4vw, 3rem).

Для компонента в колонке вместо vw удобнее единицы контейнера cqi.

Добавление rem не гарантирует доступность само по себе: проверьте фактическое увеличение текста до 200% и отсутствие обрезания. Компонент в vw может частично компенсировать увеличение при масштабе страницы.

Уточняющие вопросы

Чем clamp() отличается от min() и max()?

min() берёт меньшее, max() — большее. clamp(a, b, c) — это max(a, min(b, c)).

Почему максимальный размер стоит ограничивать?

На очень широких мониторах заголовок без ограничения станет огромным, а строки — слишком короткими.

Что прозвучало в вашем ответе
  • Назван clamp(min, preferred, max).
  • Предпочтительное значение — смесь rem и vw.
  • Объяснено, почему только vw мешает увеличению текста.

Почитать на сайте

Спецификация: CSS Values 4: clamp()

CSS и раскладка · Middle и выше

При загрузке статьи текст несколько раз «прыгает» вниз: появляется баннер, картинка, шрифт. Как называется эта проблема, как её измерить и устранить средствами CSS и HTML?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем width и height, если ширина задана в CSS как 100%?
  • Чем помогает content-visibility: auto?

Разбор

CLS — максимальная сумма неожиданных сдвигов за окно активности: до 5 секунд с паузами между сдвигами меньше секунды. Хорошее значение — не больше 0.1. Смотрят в Lighthouse, во вкладке Performance (пункт Layout Shifts) и в данных реальных пользователей.

Частые причины и решения:

  • Картинки без размеров — браузер не знает высоту, пока файл не загрузится. Атрибуты width и height дают пропорции, CSS height: auto сохраняет адаптивность. Для фона и блоков — aspect-ratio: 16 / 9;
  • Реклама, баннеры, фреймы — min-height у места под них;
  • Шрифты — смена запасного шрифта на свой меняет ширину строк. Помогают size-adjust и ascent-override в @font-face для запасного шрифта и preload основного;
  • Вставка сверху — уведомление о cookie или баннер «скачайте приложение» над содержимым. Лучше показывать поверх (position: fixed) или в зарезервированном месте;
  • Анимация размеров — анимировать transform, а не height и top.

Из CLS исключают сдвиги в течение 500 мс после подходящего пользовательского ввода, например клика или нажатия клавиши. Прокрутка и движение указателя такого исключения не дают.

Уточняющие вопросы

Зачем width и height, если ширина задана в CSS как 100%?

Браузер берёт из атрибутов соотношение сторон и вычисляет высоту до загрузки файла.

Чем помогает content-visibility: auto?

Откладывает отрисовку блоков за пределами экрана. Вместе с contain-intrinsic-size — чтобы у отложенного блока была примерная высота и полоса прокрутки не прыгала.

Что прозвучало в вашем ответе
  • Названа метрика CLS и способ измерить.
  • Резервирование места: размеры картинок, aspect-ratio или min-height.
  • Названа проблема шрифтов или вставки содержимого сверху.

Почитать на сайте

Спецификация: CSS Sizing 4: aspect-ratio

CSS и раскладка · Junior

Фотографии товаров разных пропорций нужно показать в карточках одинаковой высоты без растягивания лиц. Чем помогут aspect-ratio, object-fit и object-position?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что произойдёт, если задать и ширину, и высоту явно?
  • Изменяет ли обрезка исходный файл?

Разбор

CSS
.photo {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: center top;
}

Здесь ширина задана, высота остаётся автоматической и получается из пропорции. object-fit меняет размещение изображения внутри его бокса, а не размер самой карточки. Если важен весь товар или схема, предпочтительнее contain. Обрезку проверяют на разных фотографиях.

Уточняющие вопросы

Что произойдёт, если задать и ширину, и высоту явно?

aspect-ratio не пересчитает две уже определённые стороны; изображение впишется в заданный бокс по object-fit.

Изменяет ли обрезка исходный файл?

Нет, это только отображение; браузер загружает исходный ресурс целиком.

Что прозвучало в вашем ответе
  • Различены размер бокса и размещение изображения.
  • Объяснены обрезка cover и полосы contain.
  • Выбрано положение изображения с учётом важных деталей.

Спецификация: Первоисточник: css

CSS и раскладка · Junior

Чтобы отключить кнопку отправки, ей добавили pointer-events: none. Можно ли всё ещё отправить форму с клавиатуры и как правильно запретить действие?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему недостаточно изменить только цвет кнопки?
  • Зачем проверять флаг загрузки в обработчике формы?

Разбор

CSS не задаёт состояние недоступности элемента. Кнопка с pointer-events: none может оставаться в обходе Tab, а клик попадёт в элемент под ней. button.disabled = true сообщает браузеру нативное состояние.

Если элемент по сценарию должен оставаться фокусируемым, возможен aria-disabled="true", но запрет активации тогда реализуется кодом. Отправку Enter и программный вызов проверяют отдельно; защита от дублирования данных также нужна на сервере.

Уточняющие вопросы

Почему недостаточно изменить только цвет кнопки?

Цвет не меняет поведения и не сообщает состояние вспомогательным технологиям.

Зачем проверять флаг загрузки в обработчике формы?

Вызвать операцию может другой путь интерфейса; единая проверка предотвращает повторный запуск, пока текущий ещё идёт.

Что прозвучало в вашем ответе
  • Названо сохранение клавиатурной доступности при pointer-events: none.
  • Предложен нативный disabled.
  • Для aria-disabled требуется отдельный запрет действия.

Почитать на сайте

Спецификация: Первоисточник: css

CSS и раскладка · Middle и выше

При печати статьи пропадает часть текста, а закреплённая шапка повторяется поверх каждой страницы. Как подготовить отдельное оформление для печати?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему нельзя передать смысл только цветным фоном?
  • Нужно ли ставить break-inside: avoid на всю статью?

Разбор

CSS
@media print {
  nav, .actions { display: none; }
  .header { position: static; }
  .article { height: auto; overflow: visible; color: #000; background: #fff; }
  h2 { break-after: avoid; }
  figure { break-inside: avoid; }
}

Правила применяют к реальным блокам проекта, а не скрывают все элементы по тегу без проверки. Для полей страницы есть @page. Предпросмотр печати проверяют на длинной статье и таблицах: браузер вправе разорвать слишком большой блок, а фон может не печататься по настройкам пользователя.

Уточняющие вопросы

Почему нельзя передать смысл только цветным фоном?

Пользователь может отключить печать фонов; подписи и границы должны оставаться понятными.

Нужно ли ставить break-inside: avoid на всю статью?

Нет: статья длиннее страницы. Ограничение применяют к небольшим логическим блокам и проверяют фактические разрывы.

Что прозвучало в вашем ответе
  • Предложен @media print.
  • Сняты ограничения высоты, переполнения и фиксированного позиционирования.
  • Разрывы проверяются на длинном содержимом и в предпросмотре.

Почитать на сайте

Спецификация: Первоисточник: css

CSS и раскладка · Middle и выше

Новый эффект CSS работает не во всех целевых браузерах. Как добавить его так, чтобы основной текст и действия оставались доступны?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему не определять поддержку по названию браузера?
  • Что проверить кроме обычного экрана?

Разбор

CSS
.title { color: #174b73; }
@supports (background-clip: text) {
  .title {
    background: linear-gradient(90deg, #174b73, #6d287d);
    background-clip: text;
    color: transparent;
  }
}

Прозрачный текст включён только вместе с эффектом. В режиме печати или принудительных цветов стоит вернуть обычный цвет и убрать фон. Для селекторов доступна проверка @supports selector(:has(*)). Условие проверяет распознавание конструкции, а не внешний вид и доступность.

Уточняющие вопросы

Почему не определять поддержку по названию браузера?

Версии и реализации меняются, а браузер может иметь разные настройки. Проверка конкретной возможности ближе к задаче.

Что проверить кроме обычного экрана?

Контраст, принудительные цвета, печать и сохранение читаемости при отключённом улучшении.

Что прозвучало в вашем ответе
  • Базовый вариант работает без нового эффекта.
  • Улучшение включается через @supports.
  • Поддержка синтаксиса отличена от проверки результата.

Спецификация: Первоисточник: css

JavaScript · Junior

Каковы три результата вызовов и почему два счётчика не делят одно значение?

                        function makeCounter() {
  let value = 0;
  return () => ++value;
}
const a = makeCounter();
const b = makeCounter();
console.log(a(), a(), b());
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Где замыкания используются на практике?
  • Может ли замыкание вызвать утечку памяти?

Разбор

a() вернёт 1, затем 2; b() вернёт 1. Замыкание сохраняет доступ к переменной, а не только снимок её начального значения. У a и b разные окружения, потому что makeCounter вызвана дважды. Такой механизм полезен для скрытого состояния, но долгоживущая функция также может удерживать ненужные объекты из своего окружения.

Уточняющие вопросы

Где замыкания используются на практике?

Обработчики событий с данными из внешней функции, debounce и throttle, фабрики функций, приватное состояние модуля.

Может ли замыкание вызвать утечку памяти?

Да, если долгоживущая функция, например обработчик на window, держит ссылку на большие данные или удалённые DOM-элементы. Их не соберёт сборщик мусора, пока жива функция.

Что прозвучало в вашем ответе
  • Результаты: 1, 2, 1.
  • Возвращённая функция сохраняет доступ к лексическому окружению.
  • Каждый вызов makeCounter создаёт отдельную переменную value.

Напишите кодом

Почитать на сайте

Спецификация: ECMAScript: лексические окружения

JavaScript · Junior

Изменится ли original после этого кода? Как обновить имя, не изменяя исходный объект?

                        const original = { user: { name: "Ира" } };
const copy = { ...original };
copy.user.name = "Олег";
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сделать полную глубокую копию?
  • Почему не JSON.parse(JSON.stringify(obj))?

Разбор

original.user.name станет «Олег»: оба верхних объекта ссылаются на один user. Для изменения используйте { ...original, user: { ...original.user, name: "Олег" } }. Полное глубокое копирование ради одного поля обычно избыточно. Важно копировать именно изменяемую ветку, а не считать spread универсальной защитой от мутаций.

Уточняющие вопросы

Как сделать полную глубокую копию?

structuredClone(obj). Он копирует вложенные объекты, массивы, даты, Map и Set, но не функции и DOM-элементы.

Почему не JSON.parse(JSON.stringify(obj))?

Он теряет undefined, функции, даты превращает в строки и падает на циклических ссылках.

Что прозвучало в вашем ответе
  • Вложенный user общий, поэтому исходное имя станет Олег.
  • Spread копирует только верхний уровень.
  • Предложена копия изменяемой ветки user.

Почитать на сайте

Спецификация: ECMAScript: выражения

JavaScript · Junior

Нулевой лимит допустим и означает «ничего не показывать». Почему первый вариант ошибочен для limit=0 и какой вариант подходит?

                        const first = limit || 10;
const second = limit ?? 10;
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • А параметр по умолчанию function f(limit = 10)?
  • Что делает ??=?

Разбор

При limit=0 первый вариант даст 10, второй сохранит 0. || выбирает запасное значение при любом falsy-операнде; ?? ограничен null и undefined. Ни один оператор не проверяет корректность лимита: например, отрицательное число потребует отдельной проверки по правилам приложения.

Уточняющие вопросы

А параметр по умолчанию function f(limit = 10)?

Он подставляется только при undefined: f(0) и f(null) его не используют. Поэтому null придёт в функцию как есть.

Что делает ??=?

Присваивает значение, только если переменная равна null или undefined: options.limit ??= 10.

Что прозвучало в вашем ответе
  • Оператор || заменяет все falsy-значения, включая 0.
  • Оператор ?? заменяет только null и undefined.
  • Проверка диапазона/типа остаётся отдельной задачей.

Напишите кодом

Спецификация: ECMAScript: выражения

JavaScript · Junior

Чем отличаются var, let и const? Можно ли изменить массив, объявленный через const?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что выведет цикл for (var i = 0; i < 3; i++) setTimeout(() => console.log(i))?
  • Как сделать объект действительно неизменяемым?

Разбор

Область видимости. var ограничен функцией и «протекает» из блоков: переменная из if или цикла видна после него. let и const существуют только внутри своего блока { … }.

Поднятие. Объявление var поднимается в начало функции, и до строки с присваиванием переменная равна undefined. let и const тоже известны с начала блока, но до объявления находятся во «временной мёртвой зоне»: обращение вызывает ReferenceError. Это защищает от использования переменной до её значения.

Изменяемость. const нельзя переприсвоить: const list = []; list = [1]; — ошибка. Но list.push(1) работает: меняется содержимое массива, а не связь переменной с ним.

Правило на практике: по умолчанию const, let — когда значение действительно меняется, var в новом коде не используют.

Уточняющие вопросы

Что выведет цикл for (var i = 0; i < 3; i++) setTimeout(() => console.log(i))?

Три раза 3: у var одна переменная на весь цикл, и к моменту срабатывания таймеров она уже равна 3. С let у каждой итерации своя переменная, и вывод будет 0, 1, 2.

Как сделать объект действительно неизменяемым?

Object.freeze(obj) запрещает добавлять, удалять и менять свойства. Но заморозка поверхностная: вложенные объекты остаются изменяемыми.

Что прозвучало в вашем ответе
  • let и const — блочная область видимости, var — функциональная.
  • var до объявления равен undefined, let и const вызывают ошибку.
  • const запрещает переприсваивание, но объект внутри можно менять.
  • Названо правило: const по умолчанию, let при изменении, без var.

Спецификация: ECMAScript: let и const

JavaScript · Junior

Чем отличается == от ===? Что вернут эти сравнения?

                        0 == ''
'1' == 1
null == undefined
null === undefined
NaN === NaN
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сравнить два массива по содержимому?
  • Чем Object.is отличается от ===?

Разбор

=== не приводит типы: '1' === 1 — false. == пытается привести операнды: строку к числу, true к 1 и так далее. Поэтому 0 == '' и '1' == 1 дают true — пустая строка становится 0, а '1' — числом 1.

null == undefined — true по специальному правилу, при этом ни null, ни undefined не равны через == ничему другому. Отсюда единственное популярное применение нестрогого сравнения: value == null проверяет сразу и null, и undefined.

NaN не равен ничему, даже самому себе. Проверять его нужно через Number.isNaN(x).

Объекты, массивы и функции сравниваются по ссылке: [] === [] и {} === {} — false, потому что это два разных объекта.

Уточняющие вопросы

Как сравнить два массива по содержимому?

Встроенного способа нет. Для простых массивов сравнивают длину и элементы: a.length === b.length && a.every((x, i) => x === b[i]). Для вложенных структур нужна рекурсивная функция или библиотека.

Чем Object.is отличается от ===?

Отличий два: Object.is(NaN, NaN) — true, а Object.is(0, -0) — false. В остальном работает как ===.

Что прозвучало в вашем ответе
  • === сравнивает без приведения типов, == приводит.
  • Названы правильные результаты: true, true, true, false, false.
  • null == undefined — особое правило.
  • NaN не равен сам себе, проверка через Number.isNaN.
  • Объекты сравниваются по ссылке.

Напишите кодом

Почитать на сайте

Спецификация: ECMAScript: сравнение на равенство

JavaScript · Junior

Какие типы данных есть в JavaScript? Чем примитивы отличаются от объектов? Что вернёт typeof null?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему у строки можно вызвать метод, если это примитив?
  • Чем undefined отличается от null?

Разбор

Примитивы — простые неизменяемые значения. Когда вы присваиваете примитив другой переменной, копируется само значение. Методы вроде 'abc'.toUpperCase() не меняют строку, а возвращают новую.

Всё остальное — объекты: обычные объекты, массивы, функции, даты. В переменной хранится ссылка, поэтому const b = a не создаёт копию: обе переменные указывают на один объект, и изменение через b видно через a.

typeof возвращает строку с типом, но с особенностями: typeof null === 'object', typeof [] — тоже 'object', а typeof function(){} — 'function'. Поэтому массив проверяют через Array.isArray(), а null — сравнением value === null.

Уточняющие вопросы

Почему у строки можно вызвать метод, если это примитив?

При обращении к свойству JavaScript временно оборачивает примитив в объект-обёртку String, вызывает метод и выбрасывает обёртку. Создавать обёртки вручную через new String() не нужно.

Чем undefined отличается от null?

undefined — значение «ещё не задано»: неинициализированная переменная, отсутствующее свойство, функция без return. null ставят явно, чтобы сказать «значения нет». typeof undefined — 'undefined', а typeof null — 'object'.

Что прозвучало в вашем ответе
  • Названы примитивы (хотя бы string, number, boolean, undefined, null).
  • Названы объекты, включая массивы и функции.
  • Примитивы копируются по значению, объекты — по ссылке.
  • typeof null возвращает 'object' — историческая ошибка.
  • Array.isArray для проверки массива.

Почитать на сайте

Спецификация: ECMAScript: типы языка

JavaScript · Junior

Какие значения в JavaScript считаются ложными в условии if? Почему условие if (items.length) работает, а if (count) может подвести?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что вернёт выражение 'Ира' && 0 && 'ok'?
  • Как явно превратить значение в логическое?

Разбор

В условиях, в !, && и || значение приводится к логическому. Ложными (falsy) считаются false, 0, -0, 0n, '', null, undefined, NaN.

Неочевидные истинные значения: строка '0' и 'false', пустой массив [], пустой объект {}. Поэтому пустоту массива проверяют по длине: if (items.length === 0).

Проблема if (count) в том, что 0 и пустая строка могут быть нормальными данными. Если нужно отличить «нет значения» от нуля, проверяют явно: count !== undefined или count != null. То же с запасными значениями: limit || 10 заменит и ноль, а limit ?? 10 — только null и undefined.

Уточняющие вопросы

Что вернёт выражение 'Ира' && 0 && 'ok'?

0. Оператор && возвращает первое ложное значение или последнее, если все истинны. Оператор || — первое истинное или последнее. Они возвращают сами значения, а не true и false.

Как явно превратить значение в логическое?

Boolean(value) или двойное отрицание !!value.

Что прозвучало в вашем ответе
  • Перечислены ложные значения (хотя бы false, 0, '', null, undefined, NaN).
  • Пустой массив, пустой объект и '0' — истинные.
  • Объяснена проблема с нулём как допустимым значением.
  • Назван оператор ?? или явная проверка на null/undefined.

Спецификация: ECMAScript: ToBoolean

JavaScript · Junior

Чем объявление функции (function declaration) отличается от функционального выражения и стрелочной функции?

                        sayHi()
function sayHi() { return 'Привет' }

const sayBye = function () { return 'Пока' }
const double = (x) => x * 2
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как вернуть объект из короткой стрелочной функции?
  • Что будет с this, если передать метод объекта в setTimeout?

Разбор

Function declaration — function sayHi() {} на отдельной строке. Такие функции создаются до выполнения кода своей области, поэтому вызов sayHi() выше объявления работает.

Function expression — функция как значение: const sayBye = function () {}. Она создаётся, когда выполнение доходит до этой строки. Её удобно передавать как аргумент или возвращать из другой функции.

Стрелочная функция — короткая запись выражения: (x) => x * 2; если тело — одно выражение, оно возвращается без return. Главное отличие не в записи: у стрелки нет собственного this. Внутри метода объекта или обработчика она использует this внешнего кода. Это удобно для колбэков внутри методов, но стрелку не используют как метод объекта, которому нужен this этого объекта.

Уточняющие вопросы

Как вернуть объект из короткой стрелочной функции?

Обернуть его в скобки: () => ({ id: 1 }). Без скобок фигурные скобки считаются телом функции, и она вернёт undefined.

Что будет с this, если передать метод объекта в setTimeout?

Обычная функция потеряет объект: this определяется тем, как функция вызвана, а setTimeout вызывает её без объекта. Помогают setTimeout(() => user.say()) или user.say.bind(user).

Что прозвучало в вашем ответе
  • Function declaration поднимается и доступна до объявления.
  • Function expression доступна только после присваивания.
  • У стрелочной функции нет своего this.
  • Стрелку нельзя вызвать через new, у неё нет arguments.

Почитать на сайте

Спецификация: ECMAScript: стрелочные функции

JavaScript · Junior

Есть массив товаров. Как получить массив названий товаров дороже 1000 и их общую стоимость? Чем map отличается от forEach?

                        const products = [
  { title: 'Рюкзак', price: 3200 },
  { title: 'Ручка', price: 90 },
  { title: 'Лампа', price: 1500 },
]
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Какие методы массива меняют исходный массив?
  • Почему [10, 9, 1].sort() даёт [1, 10, 9]?

Разбор

JavaScript
const expensive = products.filter((p) => p.price > 1000)
const titles = expensive.map((p) => p.title) // ['Рюкзак', 'Лампа']
const total = expensive.reduce((sum, p) => sum + p.price, 0) // 4700

filter возвращает новый массив из элементов, для которых функция вернула true. map возвращает новый массив, где каждый элемент заменён результатом функции. reduce проходит по массиву с накопителем; начальное значение 0 важно — без него пустой массив вызовет ошибку.

forEach всегда возвращает undefined. Если внутри forEach собирать результат через push в другой массив, это значит, что нужен map или filter. Ещё find возвращает первый подходящий элемент, some и every — проверяют, есть ли хотя бы один подходящий и подходят ли все.

Уточняющие вопросы

Какие методы массива меняют исходный массив?

push, pop, shift, unshift, splice, sort, reverse и fill. Для sort и reverse есть копирующие варианты toSorted и toReversed. Если массив пришёл извне, перед sort стоит сделать копию: [...items].sort().

Почему [10, 9, 1].sort() даёт [1, 10, 9]?

Без функции сравнения sort сравнивает элементы как строки. Для чисел передают функцию: items.sort((a, b) => a - b).

Что прозвучало в вашем ответе
  • Решение через filter, map и reduce с правильным результатом.
  • map возвращает новый массив, forEach — undefined.
  • Методы не меняют исходный массив.
  • У reduce указано начальное значение.

Почитать на сайте

JavaScript · Junior

Как повесить обработчик клика на кнопку? Чем event.target отличается от event.currentTarget и что такое всплытие?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему querySelector вернул null?
  • Как найти кнопку, если кликнули по иконке внутри неё?

Разбор

JavaScript
const button = document.querySelector('.js-save')
button.addEventListener('click', (event) => {
  console.log(event.target, event.currentTarget)
})

addEventListener позволяет повесить несколько обработчиков на одно событие и снять их через removeEventListener с той же функцией. Атрибут onclick в HTML смешивает разметку с кодом и позволяет только один обработчик.

Всплытие: клик по иконке внутри кнопки сначала обрабатывается на иконке, затем на кнопке, затем на её родителях. Поэтому target может оказаться вложенным элементом, а currentTarget всегда тот, на ком висит обработчик. На всплытии построено делегирование: один обработчик на списке вместо обработчика на каждом пункте.

event.preventDefault() отменяет стандартное действие: переход по ссылке, отправку формы. event.stopPropagation() останавливает всплытие, но использовать его стоит осторожно — другие обработчики выше по дереву перестанут узнавать о событии.

Уточняющие вопросы

Почему querySelector вернул null?

Чаще всего скрипт выполнился до того, как элемент появился в DOM: подключён в head без defer. Решения — defer, скрипт в конце body или обработчик DOMContentLoaded. Вторая частая причина — ошибка в селекторе.

Как найти кнопку, если кликнули по иконке внутри неё?

event.target.closest('button') — ближайший предок (или сам элемент), подходящий под селектор.

Что прозвучало в вашем ответе
  • Назван addEventListener.
  • Объяснено всплытие от элемента к предкам.
  • target — где произошло событие, currentTarget — чей обработчик выполняется.
  • Названы preventDefault и его отличие от stopPropagation.

Почитать на сайте

Спецификация: DOM: события

JavaScript · Junior

Нужно загрузить список пользователей с сервера и показать его. Как написать это через async/await и обработать ошибку? Что такое Promise?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как загрузить два независимых списка параллельно?
  • Что будет, если забыть await перед fetch?

Разбор

JavaScript
async function loadUsers() {
  try {
    const response = await fetch('/api/users')
    if (!response.ok) throw new Error('HTTP ' + response.status)
    const users = await response.json()
    render(users)
  } catch (error) {
    showError('Не удалось загрузить пользователей')
  }
}

Сетевой запрос занимает время, и JavaScript не ждёт его, останавливая страницу: fetch сразу возвращает Promise. У промиса три состояния: ожидание, выполнен (fulfilled) и отклонён (rejected). Результат получают через .then() и .catch() или через await.

await можно писать только внутри async-функции (или на верхнем уровне модуля). Сама async-функция всегда возвращает Promise. Код после await продолжится, когда промис завершится, а пока браузер обрабатывает другие события.

Важная деталь fetch: промис отклоняется только при сетевой ошибке. Ответ 404 или 500 — это успешно полученный ответ, поэтому response.ok проверяют вручную.

Уточняющие вопросы

Как загрузить два независимых списка параллельно?

Запустить оба запроса сразу и ждать оба: const [users, posts] = await Promise.all([loadUsers(), loadPosts()]). Два await подряд выполнят запросы по очереди и займут вдвое больше времени.

Что будет, если забыть await перед fetch?

В переменной окажется сам Promise, а не ответ, и response.json станет ошибкой «не функция». А ошибка внутри промиса без await не попадёт в try/catch.

Что прозвучало в вашем ответе
  • Promise описан как результат будущей операции с состояниями.
  • Код с async/await и try/catch.
  • Проверяется response.ok.
  • await не блокирует страницу.
  • Упомянут Promise.all для параллельной загрузки.

Почитать на сайте

JavaScript · Junior

API может вернуть пользователя без адреса. Как безопасно прочитать город, не получив ошибку Cannot read properties of undefined?

                        const user = { name: 'Ира' }
console.log(user.address.city)
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как это писали до появления ?.
  • Можно ли присвоить значение через ?.

Разбор

user.address равно undefined, а у undefined и null нет свойств, поэтому .city вызывает TypeError и скрипт останавливается.

JavaScript
const city = user.address?.city ?? 'Город не указан'

Оператор ?. проверяет значение слева: если это null или undefined, всё выражение сразу возвращает undefined. Он работает и для вызовов (user.getName?.()), и для индексов (list?.[0]). Оператор ?? подставляет запасное значение только для null и undefined, в отличие от ||, который заменит и пустую строку.

Но ?. не стоит ставить везде «на всякий случай». Если поле обязательно, его отсутствие — ошибка данных, и её лучше заметить сразу, а не показывать пустое место.

Уточняющие вопросы

Как это писали до появления ?.

Через цепочку &&: user.address && user.address.city. Работает, но длиннее и возвращает falsy-значение, а не undefined.

Можно ли присвоить значение через ?.

Нет: user.address?.city = 'Пермь' — синтаксическая ошибка. Опциональная цепочка только читает.

Что прозвучало в вашем ответе
  • Объяснена причина ошибки: обращение к свойству undefined.
  • Назван оператор ?.
  • Назван ?? для запасного значения и его отличие от ||.
  • Упомянуто, что обязательные поля не стоит маскировать.

Напишите кодом

Спецификация: ECMAScript: optional chaining

JavaScript · Middle и выше

Почему при передаче метода отдельно теряется this? Покажите два способа передать вызов say как callback с сохранением объекта.

                        const user = {
  name: "Ира",
  say() { return this.name; }
};
const callback = user.say;
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем call и apply отличаются от bind?
  • Почему в классах методы иногда пишут стрелками в полях?

Разбор

Вызов user.say() передаёт user как this, а отдельный callback не сохраняет это правило автоматически. Передайте user.say.bind(user) или обёртку () => user.say(). Если функцию потом нужно передать removeEventListener, сохраните её в переменной: повторный bind создаст другой объект функции. Стрелка вместо метода не получает this объекта автоматически.

Уточняющие вопросы

Чем call и apply отличаются от bind?

call и apply сразу вызывают функцию с указанным this (apply принимает аргументы массивом), а bind возвращает новую функцию с привязанным this.

Почему в классах методы иногда пишут стрелками в полях?

handleClick = () => {…} создаёт для каждого экземпляра функцию с привязанным this, и её можно передавать как обработчик. Цена — отдельная функция на каждый объект.

Что прозвучало в вашем ответе
  • this обычной функции определяется вызовом, а не местом объявления в объекте.
  • Предложены user.say.bind(user) и () => user.say().
  • Указано, что bind создаёт новую функцию; для снятия обработчика нужна сохранённая ссылка.

Почитать на сайте

Спецификация: ECMAScript: связанные функции

JavaScript · Middle и выше

В каком порядке появятся сообщения в обычном скрипте браузера? Почему таймер с нулевой задержкой не выполняется сразу?

                        console.log("start");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("end");
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Куда попадает код после await?
  • Почему setTimeout(fn, 0) иногда используют специально?

Разбор

Порядок: start, end, promise, timer. Сначала завершается текущий синхронный код. Реакция уже выполненного Promise попадает в микрозадачи и обрабатывается до следующей задачи таймера. Нулевая задержка не прерывает текущий код и не означает выполнение ровно через 0 мс. Длинная цепочка микрозадач тоже может задерживать отзывчивость страницы.

Уточняющие вопросы

Куда попадает код после await?

Продолжение после await выполняется как микрозадача, когда ожидаемый промис завершится. Если он ещё ждёт сеть или таймер, другие задачи могут выполниться раньше; обещать порядок «до всех таймеров» нельзя.

Почему setTimeout(fn, 0) иногда используют специально?

Чтобы отложить работу до следующей задачи: дать браузеру отрисовать изменения или обработать события. Для переноса на следующий кадр лучше requestAnimationFrame.

Что прозвучало в вашем ответе
  • Порядок: start, end, promise, timer.
  • Синхронный код завершается до реакции Promise.
  • Микрозадачи выполняются до следующей задачи таймера; 0 не обещает точное время.

Почитать на сайте

Спецификация: WHATWG: цикл событий

JavaScript · Middle и выше

Страница загружает три независимых виджета. Ошибка одного не должна скрывать остальные. Что выбрать: Promise.all или Promise.allSettled? Отменит ли all остальные запросы при ошибке?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем Promise.race отличается от Promise.any?
  • Как действительно отменить запросы?

Разбор

Для независимых виджетов подходит allSettled: после завершения всех операций можно обработать fulfilled и rejected отдельно. all удобен, когда для результата нужны все успешные значения. Его отклонение не отменяет уже начатые запросы; отмену нужно организовать отдельно. Оба метода сохраняют порядок входных элементов в успешном массиве результатов, а не сортируют по скорости.

Уточняющие вопросы

Чем Promise.race отличается от Promise.any?

race завершается первым завершившимся промисом — успешным или нет. any ждёт первого успешного и отклоняется, только если отклонились все.

Как действительно отменить запросы?

Передать в fetch signal от AbortController и вызвать controller.abort().

Что прозвучало в вашем ответе
  • allSettled позволяет получить статус каждой операции.
  • all отклоняется при первой обработанной ошибке, но не отменяет остальные операции.
  • Порядок итоговых элементов соответствует входу, а не завершению.

Напишите кодом

Почитать на сайте

Спецификация: ECMAScript: Promise.all и Promise.allSettled

JavaScript · Middle и выше

В списке динамически добавляются кнопки удаления с вложенными иконками. Как обработать клики одним слушателем? Чем preventDefault отличается от stopPropagation?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Какие события не всплывают?
  • Зачем проверять, что найденная кнопка внутри контейнера?

Разбор

Повесьте click на контейнер. Если event.target является Element, найдите ближайшую кнопку через closest и проверьте принадлежность списку. Это работает и для новых кнопок. preventDefault не останавливает всплытие, а stopPropagation не отменяет переход по ссылке или отправку формы. Для вложенных списков дополнительно проверяйте, что кнопка относится именно к текущему списку.

Уточняющие вопросы

Какие события не всплывают?

focus, blur, mouseenter, mouseleave, load. Для фокуса есть всплывающие аналоги focusin и focusout.

Зачем проверять, что найденная кнопка внутри контейнера?

closest может найти кнопку выше контейнера, если клик был по вложенной области. Проверка list.contains(button) защищает от чужих элементов.

Что прозвучало в вашем ответе
  • Слушатель на контейнере использует всплытие и closest для вложенной иконки.
  • target проверяется как Element, найденная кнопка принадлежит нужному контейнеру.
  • preventDefault отменяет отменяемое действие по умолчанию; stopPropagation влияет на распространение.

Почитать на сайте

Спецификация: WHATWG DOM: распространение событий

JavaScript · Middle и выше

Как в JavaScript работает наследование через прототипы? Что на самом деле создаёт class Dog extends Animal?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как создать объект без прототипа и зачем?
  • Чем in отличается от Object.hasOwn()?

Разбор

При чтении dog.speak() движок ищет speak у самого объекта, затем у его прототипа (Object.getPrototypeOf(dog)), затем у прототипа прототипа и так до null. Так методы хранятся один раз и используются всеми экземплярами.

JavaScript
class Animal { speak() { return '…' } }
class Dog extends Animal { speak() { return 'Гав' } }
const dog = new Dog()
// dog → Dog.prototype → Animal.prototype → Object.prototype → null

Что делает класс:

  • методы попадают в Dog.prototype, а не копируются в каждый объект;
  • extends ставит Animal.prototype прототипом Dog.prototype, а super.speak() вызывает метод родителя;
  • в конструкторе наследника super() нужно вызвать до обращения к this;
  • в отличие от функции-конструктора, класс нельзя вызвать без new, а код внутри всегда в строгом режиме.

Поля класса (name = 'Тони') и приватные поля (#secret) создаются у каждого экземпляра, а не в прототипе.

Уточняющие вопросы

Как создать объект без прототипа и зачем?

Object.create(null). У такого объекта нет унаследованных свойств вроде toString, поэтому он подходит как чистый словарь. Сегодня для этого чаще берут Map.

Чем in отличается от Object.hasOwn()?

in ищет свойство и в цепочке прототипов, Object.hasOwn(obj, key) — только у самого объекта.

Что прозвучало в вашем ответе
  • Поиск свойства идёт по цепочке прототипов.
  • Методы класса лежат в prototype и общие для экземпляров.
  • extends связывает прототипы, super вызывает родителя.
  • Класс — синтаксис над прототипами, а не отдельная модель.

Почитать на сайте

Спецификация: ECMAScript: определения классов

JavaScript · Middle и выше

Чем ES-модули (import/export) отличаются от CommonJS (require)? Как подключить тяжёлую библиотеку графиков только тогда, когда пользователь открыл вкладку со статистикой?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем именованный экспорт отличается от экспорта по умолчанию?
  • Что мешает tree shaking?

Разбор

ES-модули. import и export пишутся на верхнем уровне, и связи между файлами известны заранее. Это даёт tree shaking, раннюю загрузку зависимостей и понятные ошибки, если имени нет. Импорт — живая привязка: если модуль поменяет экспортированную переменную, импортирующий это увидит. В браузере подключаются через <script type="module">, который по умолчанию ведёт себя как defer.

CommonJS. require() — обычная функция: её можно вызвать в условии, а результат — значение module.exports, обычно из кеша модуля. Если это объект, возвращается ссылка на него, а не копия; последующее переназначение module.exports не меняет уже полученную ссылку. Сборщику труднее понять, что используется.

Ленивая загрузка.

JavaScript
tab.addEventListener('click', async () => {
  const { drawChart } = await import('./chart.js')
  drawChart(data)
})

Сборщик выносит chart.js и его зависимости в отдельный файл, который загрузится только при первом щелчке. Стоит показать индикатор загрузки и обработать ошибку сети.

Уточняющие вопросы

Чем именованный экспорт отличается от экспорта по умолчанию?

Именованный импортируют под тем же именем (import { drawChart }), и редактор подскажет его и при переименовании. Экспорт по умолчанию каждый называет как хочет, из-за чего одно и то же в проекте называется по-разному.

Что мешает tree shaking?

Побочные эффекты при импорте, например код, который при загрузке меняет глобальные объекты, и CommonJS. Флаг sideEffects в package.json подсказывает сборщику, что выкидывать безопасно.

Что прозвучало в вашем ответе
  • ES-модули статические, CommonJS — вызов во время выполнения.
  • Названы tree shaking или раннее знание зависимостей.
  • Ленивая загрузка через динамический import().
  • Упомянуты индикатор загрузки или обработка ошибки.

Почитать на сайте

Спецификация: ECMAScript: модули

JavaScript · Middle и выше

Одностраничное приложение через час работы занимает гигабайт памяти. Как устроена сборка мусора в JavaScript и какие ошибки в коде её ломают? Где тут поможет WeakMap?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как удобно снять сразу несколько слушателей?
  • Почему нельзя перебрать WeakMap?

Разбор

Объект живёт, пока до него есть путь по ссылкам от корней. Сборщик периодически помечает всё достижимое и освобождает остальное. Поэтому утечка в JavaScript — это не «забыли освободить», а «случайно держим ссылку».

Типичные причины:

  • слушатели на window и document, которые не сняли при уходе со страницы, — они держат обработчик и всё, что он замкнул;
  • setInterval без clearInterval;
  • кеш в Map или массиве, который только растёт;
  • удалённый из страницы элемент, на который осталась ссылка в переменной или массиве;
  • подписки на хранилище или сокет без отписки.

WeakMap хранит ключи слабо: если на объект-ключ больше нет ссылок, запись удаляется вместе с ним. Это удобно для данных «к элементу» или «к объекту» без ручной очистки. У WeakMap нет перебора и размера — именно поэтому сборщик может удалять записи когда угодно.

Искать утечку: вкладка Memory в DevTools, два снимка кучи до и после повторения действия и сравнение того, что выросло.

Уточняющие вопросы

Как удобно снять сразу несколько слушателей?

Передать всем один signal от AbortController: addEventListener('resize', fn, { signal }), а при очистке вызвать controller.abort().

Почему нельзя перебрать WeakMap?

Её содержимое зависит от того, когда прошла сборка мусора. Перебор давал бы разный результат в разное время, поэтому его нет.

Что прозвучало в вашем ответе
  • Сборщик удаляет недостижимые объекты.
  • Утечка — лишняя ссылка; названы хотя бы две причины.
  • WeakMap не удерживает ключ, нет перебора.
  • Названа диагностика: снимки памяти в DevTools.

Почитать на сайте

Спецификация: ECMAScript: WeakMap

JavaScript · Middle и выше

Поле поиска отправляет запрос на каждый символ, а обработчик прокрутки пересчитывает положение элементов сотни раз в секунду. Что такое debounce и throttle, что выбрать в каждом случае и как устроен debounce?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что такое leading и trailing вызов?
  • Как отменить отложенный вызов, например при закрытии окна поиска?

Разбор

Оба способа ограничивают частоту вызовов, но по-разному:

  • debounce ждёт, пока события прекратятся: человек перестал печатать на 300 мс — отправляем запрос;
  • throttle пропускает не больше одного вызова за интервал: при прокрутке положение пересчитывается, скажем, раз в 100 мс, а не на каждый пиксель.
JavaScript
function debounce(fn, delay) {
  let timer
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

input.addEventListener('input', debounce(search, 300))

Замыкание хранит timer между вызовами, а apply сохраняет this и аргументы. Для визуальных обновлений при прокрутке часто лучше requestAnimationFrame — он синхронизирован с кадрами — или IntersectionObserver, если нужно знать, виден ли элемент.

Debounce не решает гонку ответов: старый запрос всё ещё может прийти позже нового. Это отдельная задача — отмена через AbortController или проверка, что ответ актуален.

Уточняющие вопросы

Что такое leading и trailing вызов?

Leading — вызвать сразу на первом событии, trailing — в конце серии. Для кнопки «Отправить» с защитой от двойного нажатия нужен leading, для поиска — trailing.

Как отменить отложенный вызов, например при закрытии окна поиска?

Вернуть из debounce функцию с методом cancel(), который делает clearTimeout(timer).

Что прозвучало в вашем ответе
  • Debounce — после паузы, throttle — не чаще интервала.
  • Поиск — debounce, прокрутка — throttle или requestAnimationFrame.
  • Debounce устроен как сбрасываемый таймер в замыкании.
  • Сохраняются this и аргументы.

Напишите кодом

Почитать на сайте

Спецификация: HTML: таймеры

JavaScript · Junior

Что делают const { name, city = 'Москва' } = user и const [first, ...rest] = items? Чем оператор ... в вызове функции отличается от ... в параметрах?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что будет, если деструктурировать undefined?
  • Как поменять местами две переменные?

Разбор

Деструктуризация:

JavaScript
const { name, city = 'Москва' } = user   // city — если в user его нет или undefined
const { name: title } = user              // переименование
const [first, ...rest] = [1, 2, 3]       // first = 1, rest = [2, 3]
function show({ id, price }) { … }        // прямо в параметрах

Одни и те же три точки делают противоположное:

  • rest (собрать) — в параметрах function sum(...nums) и в деструктуризации const { id, ...other } = obj;
  • spread (развернуть) — в вызове Math.max(...nums), в массиве [...a, ...b], в объекте { ...defaults, ...options }.

При слиянии объектов побеждает свойство, записанное позже: { ...defaults, ...options } — настройки пользователя перекроют умолчания. Копия поверхностная: вложенные объекты остаются общими.

Уточняющие вопросы

Что будет, если деструктурировать undefined?

Ошибка TypeError. Для необязательного аргумента пишут значение по умолчанию: function f({ id } = {}).

Как поменять местами две переменные?

[a, b] = [b, a].

Что прозвучало в вашем ответе
  • Объяснена деструктуризация объекта по именам и массива по порядку.
  • Названо значение по умолчанию.
  • Rest собирает, spread разворачивает; приведены примеры.
  • Копия через spread поверхностная.

Спецификация: ECMAScript: деструктуризация

JavaScript · Junior

Нужно убрать повторы из списка тегов и посчитать, сколько раз встречается каждое слово в тексте. Когда для этого подходят Set и Map и чем Map лучше обычного объекта?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как Set сравнивает объекты?
  • Как получить пять самых частых слов?

Разбор

JavaScript
const unique = [...new Set(['html', 'css', 'html'])] // ['html', 'css']

const counts = new Map()
for (const word of text.split(' ')) {
  counts.set(word, (counts.get(word) ?? 0) + 1)
}

Set проверяет наличие через has() быстро, без перебора массива, — удобно для «уже видели этот id?».

Map лучше объекта для словаря, потому что:

  • ключ может быть любым: число, объект, DOM-элемент. Ключи обычного объекта — строки или символы; остальные значения преобразуются в строки;
  • нет унаследованных ключей: слово constructor или toString не совпадёт со встроенным свойством;
  • есть size, а перебор идёт в порядке добавления.

Обычный объект остаётся удобнее для данных с известными полями и для JSON: Map не превращается в JSON напрямую — нужно Object.fromEntries(map).

Уточняющие вопросы

Как Set сравнивает объекты?

По ссылке: два разных объекта с одинаковыми полями — разные значения. Для уникальности по id берут Map с ключом id.

Как получить пять самых частых слов?

[...counts].sort((a, b) => b[1] - a[1]).slice(0, 5).

Что прозвучало в вашем ответе
  • Set хранит уникальные значения, убирает повторы.
  • Map хранит пары с любыми ключами.
  • Назван подсчёт через get/set.
  • Названо отличие от объекта: ключи, size или порядок.

Напишите кодом

Почитать на сайте

Спецификация: ECMAScript: Map

JavaScript · Junior

Нужно искать город в списке по вводу пользователя: « мос» должно найти «Москва». Какие методы строк понадобятся и что учесть?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как подсветить совпадение в найденном названии?
  • Как отсортировать города по алфавиту правильно?

Разбор

JavaScript
function findCities(cities, input) {
  const query = input.trim().toLowerCase().replaceAll('ё', 'е')
  if (!query) return []
  return cities.filter((city) =>
    city.toLowerCase().replaceAll('ё', 'е').startsWith(query)
  )
}
  • trim() убирает пробелы по краям — их часто вставляют случайно;
  • toLowerCase() у обеих строк — иначе «мос» не найдёт «Москва»;
  • startsWith() — совпадение с начала, includes() — в любом месте;
  • пустой запрос лучше не искать: '' находится в любой строке;
  • «ё» и «е» пользователи пишут вперемешку — их стоит считать одинаковыми.

Строки в JavaScript неизменяемы: input.trim() не меняет input, а возвращает новую строку. Для сравнения с учётом языка есть localeCompare() и Intl.Collator.

Уточняющие вопросы

Как подсветить совпадение в найденном названии?

Найти позицию через indexOf, разрезать строку slice на три части и обернуть середину в элемент. Вставлять через textContent и создание элементов, а не через innerHTML с вводом пользователя.

Как отсортировать города по алфавиту правильно?

cities.sort((a, b) => a.localeCompare(b, 'ru')): обычное сравнение строк ставит заглавные и «ё» не туда.

Что прозвучало в вашем ответе
  • Названы trim() и toLowerCase().
  • Названы startsWith() или includes().
  • Учтён пустой запрос или «ё».
  • Строки неизменяемы, методы возвращают новую строку.

Почитать на сайте

Спецификация: ECMAScript: методы String

JavaScript · Middle и выше

Как работает цикл for...of «под капотом»? Что такое генератор и где он полезен на практике?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему for...of не работает с обычным объектом?
  • Чем for...of отличается от for...in?

Разбор

Протокол итерации: объект перебираемый, если у него есть метод [Symbol.iterator], который возвращает объект с next(). Каждый вызов next() даёт { value, done }. Так перебираются массивы, строки, Map, Set, и на этом же работают spread [...x] и деструктуризация.

JavaScript
function* range(from, to) {
  for (let i = from; i <= to; i++) yield i
}
[...range(1, 3)] // [1, 2, 3]

function* walk(node) {
  yield node
  for (const child of node.children) yield* walk(child)
}

Генератор выполняется по шагам: до первого yield, затем пауза до следующего next(). Поэтому значения считаются лениво — бесконечная последовательность не зависнет, если брать из неё понемногу.

Асинхронный генератор async function* с for await...of удобен для постраничной загрузки: функция сама запрашивает следующую страницу, когда предыдущая закончилась, а вызывающий код просто перебирает элементы.

Уточняющие вопросы

Почему for...of не работает с обычным объектом?

У него нет [Symbol.iterator]. Перебирают Object.keys(obj), Object.values(obj) или Object.entries(obj).

Чем for...of отличается от for...in?

for...of перебирает значения через итератор, for...in — строковые ключи объекта, включая унаследованные. Для массивов for...in не используют.

Что прозвучало в вашем ответе
  • Назван протокол итерации: Symbol.iterator и next().
  • Генератор через function* и yield, выполнение по шагам.
  • Назван практический пример: ленивая последовательность, обход дерева или постраничная загрузка.

Спецификация: ECMAScript: генераторы

JavaScript · Middle и выше

Как организовать обработку ошибок в приложении, чтобы пользователь видел понятное сообщение, а разработчики узнавали о сбоях? Почему try/catch вокруг setTimeout не ловит ошибку внутри?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что будет с промисом, у которого нет catch?
  • Что показывать пользователю, а что — разработчику?

Разбор

Где ловить. Ошибку ловят там, где знают, что с ней делать: в функции загрузки — показать «Не удалось загрузить, повторить?», в форме — сообщение у поля. Ловить всё подряд и молча проглатывать — хуже, чем не ловить.

Асинхронность. try/catch работает только для кода, выполняющегося внутри блока сейчас:

JavaScript
try {
  setTimeout(() => { throw new Error('!') }, 0) // не поймается
} catch {}

try {
  await loadUser() // поймается
} catch (error) { showError(error) }

Свои классы ошибок. class HttpError extends Error с полем status позволяет различать сетевую ошибку, 404 и ошибку проверки. При повторном выбрасывании сохраняют причину: new Error('Не удалось сохранить', { cause: error }).

Верхний уровень. window.addEventListener('error', …) и 'unhandledrejection' ловят всё необработанное — их отправляют в систему мониторинга вместе с адресом страницы и действием пользователя. finally снимает индикатор загрузки при любом исходе.

Уточняющие вопросы

Что будет с промисом, у которого нет catch?

При отклонении браузер выведет ошибку в консоль и вызовет событие unhandledrejection; в Node.js процесс по умолчанию завершится.

Что показывать пользователю, а что — разработчику?

Пользователю — что случилось и что делать: повторить, проверить сеть, обратиться в поддержку. Технические подробности и стек — в мониторинг, а не на экран.

Что прозвучало в вашем ответе
  • try/catch ловит синхронный код и await внутри блока.
  • Названы обработчики error и unhandledrejection или мониторинг.
  • Пользователь видит понятное состояние ошибки.
  • Названы свои классы ошибок, cause или finally.

Почитать на сайте

Спецификация: ECMAScript: оператор try

JavaScript · Junior

Функция сортирует список товаров для вывода, и после этого «ломается» исходный порядок в другом месте приложения. Почему? Какие методы массива меняют его, а какие возвращают новый?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему [10, 9, 1].sort() даёт [1, 10, 9]?
  • Защитит ли const массив от изменений?

Разбор

JavaScript
const sorted = products.sort((a, b) => a.price - b.price)
sorted === products // true — это тот же массив

const safe = products.toSorted((a, b) => a.price - b.price) // новый
// или [...products].sort(…)

Массив в переменной — это ссылка. Функция получила тот же массив, что и остальной код, и sort переставил элементы для всех.

Меняют исходный массив: push, pop, shift, unshift, splice, sort, reverse, fill, copyWithin.

Возвращают новый: map, filter, slice, concat, flat и современные toSorted, toReversed, toSpliced, with(index, value).

В интерфейсах, где важно отслеживать изменения (например, в React), массивы обновляют только через новые копии.

Уточняющие вопросы

Почему [10, 9, 1].sort() даёт [1, 10, 9]?

Без функции сравнения элементы сравниваются как строки: «10» идёт раньше «9». Для чисел нужно sort((a, b) => a - b).

Защитит ли const массив от изменений?

Нет, const запрещает только присвоить переменной другой массив. Содержимое менять можно.

Что прозвучало в вашем ответе
  • sort меняет исходный массив, массивы передаются по ссылке.
  • Названы методы, которые меняют массив.
  • Названы методы, которые возвращают новый, включая toSorted или копию.

Напишите кодом

Почитать на сайте

Спецификация: ECMAScript: Array.prototype.toSorted

JavaScript · Junior

Нужно показать дату заказа «24 октября 2026» и посчитать, через сколько дней доставка. Какие подвохи есть у Date и как вывести дату по-русски?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как получить текущую метку времени?
  • Что такое Temporal?

Разбор

Если доставка задана календарными датами без времени, сравнивайте именно даты. В примере год, месяц и день уже проверены, месяц записан как число от 1 до 12:

JavaScript
const order = { year: 2026, month: 10, day: 24 }
const delivery = { year: 2026, month: 10, day: 27 }
const dayNumber = ({ year, month, day }) =>
  Date.UTC(year, month - 1, day) / 86400000
const days = dayNumber(delivery) - dayNumber(order) // 3

new Intl.DateTimeFormat("ru-RU", {
  day: "numeric", month: "long", year: "numeric", timeZone: "UTC",
}).format(new Date(Date.UTC(order.year, order.month - 1, order.day)))

UTC здесь используется для арифметики календарных дат, а не как часовой пояс доставки. Если API присылает моменты времени, сначала определите календарные даты в часовом поясе доставки. Для длительности в часах, наоборот, вычитают метки времени. Простое округление разницы локальных дат не является универсальным исправлением.

new Date(2026, 9, 24) создаёт локальную дату, месяцы нумеруются с нуля. Строка 2026-10-24 интерпретируется как полночь UTC, а 2026-10-24T00:00 — как местное время. Методы вроде setDate меняют исходный объект.

Уточняющие вопросы

Как получить текущую метку времени?

Date.now() — число миллисекунд с 1 января 1970 по UTC.

Что такое Temporal?

Новый встроенный API для дат: неизменяемые объекты, отдельные типы для даты без времени и для времени с часовым поясом. Поддерживается ещё не во всех браузерах.

Что прозвучало в вашем ответе
  • Различены календарная дата и момент времени.
  • Объяснены нумерация месяцев с нуля и роль часового пояса.
  • Для вывода выбран Intl.DateTimeFormat; календарные дни не считаются слепым делением локальных миллисекунд.

Почитать на сайте

Спецификация: ECMAScript: объекты Date

JavaScript · Middle и выше

Нужно загрузить 200 картинок для обработки. В чём разница между await в цикле for, Promise.all(items.map(…)) и загрузкой не больше пяти одновременно? Как написать последний вариант?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему items.forEach(async (item) => await load(item)) не ждёт загрузок?
  • Сколько запросов браузер выполняет одновременно сам?

Разбор

JavaScript
async function mapLimit(items, limit, fn) {
  if (!Number.isInteger(limit) || limit < 1) throw new RangeError("limit должен быть положительным целым")
  const results = new Array(items.length)
  let next = 0
  async function worker() {
    while (next < items.length) {
      const index = next++
      results[index] = await fn(items[index], index)
    }
  }
  await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker))
  return results
}

const images = await mapLimit(urls, 5, loadImage)
  • await fn(item) внутри for...of — последовательно: нужно, когда шаги зависят друг от друга или порядок важен;
  • Promise.all — всё параллельно: хорошо для нескольких независимых запросов, плохо для сотен;
  • пул — пять работников делят общий счётчик next. Так как JavaScript однопоточный, next++ между await безопасен; результаты кладутся по индексу, порядок сохраняется.

Ошибки: в этом варианте первая ошибка отклонит общий промис, но остальные работники продолжат. Если нужны все результаты, оборачивают fn в try/catch и сохраняют { ok, value, error } — как в Promise.allSettled. Для отмены передают AbortSignal.

Уточняющие вопросы

Почему items.forEach(async (item) => await load(item)) не ждёт загрузок?

forEach не ждёт промисы, которые возвращает колбэк: он запускает все сразу и сразу заканчивается.

Сколько запросов браузер выполняет одновременно сам?

Число зависит от протокола, браузера и сервера. HTTP/2 и HTTP/3 допускают много потоков в одном соединении, но это не отменяет лимитов сервера и памяти приложения. Размер пула выбирают по измерениям и ограничениям API.

Что прозвучало в вашем ответе
  • Объяснены последовательный вариант и Promise.all.
  • Названы минусы Promise.all на сотнях элементов.
  • Описан пул с ограничением параллельности.
  • Учтены порядок результатов или обработка ошибок.

Напишите кодом

Почитать на сайте

Спецификация: ECMAScript: Promise.all

JavaScript · Middle и выше

Нужно сохранить черновик формы и дать пользователю «откатить» изменения. Почему JSON.parse(JSON.stringify(obj)) иногда портит данные и что делает structuredClone?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делает Object.freeze и защищает ли он вложенные объекты?
  • Когда JSON-копия всё-таки подходит?

Разбор

JavaScript
const draft = { updated: new Date(), tags: new Set(['html']), note: undefined }

JSON.parse(JSON.stringify(draft))
// { updated: '2026-10-24T…', tags: {} } — дата строкой, Set пустой, note пропал

structuredClone(draft)
// { updated: Date, tags: Set {'html'}, note: undefined }

structuredClone использует тот же алгоритм, что postMessage и IndexedDB:

  • сохраняет Date, RegExp, Map, Set, ArrayBuffer, Blob, вложенность и циклические ссылки;
  • бросает ошибку на функциях и DOM-узлах;
  • экземпляр класса превращается в обычный объект: методы из прототипа теряются.

Часто глубокая копия не нужна: при неизменяемых обновлениях ({ ...state, user: { ...state.user, name } }) копируется только изменённая ветка, и «откат» — это просто сохранённая ссылка на прежнее состояние.

Уточняющие вопросы

Что делает Object.freeze и защищает ли он вложенные объекты?

Запрещает менять свойства самого объекта. Заморозка поверхностная: вложенные объекты можно менять, если не заморозить их отдельно.

Когда JSON-копия всё-таки подходит?

Когда данные и так пришли из JSON и в них только строки, числа, булевы значения, массивы и простые объекты.

Что прозвучало в вашем ответе
  • Названо, что теряет JSON-копия.
  • structuredClone сохраняет даты, Map, Set и циклы.
  • Названы ограничения structuredClone: функции, DOM, прототипы.
  • Упомянуты неизменяемые обновления как альтернатива.

Почитать на сайте

Спецификация: HTML: structuredClone

JavaScript · Middle и выше

Индикатор загрузки нужно показать перед тяжёлым расчётом, но он появляется только после расчёта. Почему не помогает Promise.resolve().then(calc) и что сработает?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что такое долгая задача?
  • Почему бесконечная цепочка промисов может «повесить» страницу?

Разбор

Порядок в цикле событий: задача (обработчик клика) → все микрозадачи (промисы) → при необходимости отрисовка → следующая задача.

JavaScript
button.addEventListener('click', () => {
  spinner.hidden = false
  Promise.resolve().then(calc) // микрозадача: до отрисовки, спиннера не видно
})

button.addEventListener('click', () => {
  spinner.hidden = false
  requestAnimationFrame(() => setTimeout(calc)) // даём браузеру возможность показать кадр до расчёта
})

requestAnimationFrame вызывается прямо перед отрисовкой кадра — сам расчёт в нём всё равно задержит этот кадр. Поэтому внутри откладывают ещё раз через setTimeout, и расчёт переносится в новую задачу. Это даёт возможность отрисовки, но не гарантирует показ кадра, например в скрытой вкладке.

Но интерфейс всё равно «замрёт» на время расчёта: кнопки не нажимаются, анимация спиннера стоит. Правильные решения:

  • разбить расчёт на части и отдавать управление между ними (await scheduler.yield() там, где поддерживается, или setTimeout);
  • вынести расчёт в Web Worker — тогда основной поток свободен.

Уточняющие вопросы

Что такое долгая задача?

Задача дольше 50 мс. В это время страница не отвечает на ввод; во вкладке Performance они отмечены красным.

Почему бесконечная цепочка промисов может «повесить» страницу?

Микрозадачи выполняются, пока очередь не опустеет. Если каждая добавляет новую, браузер так и не дойдёт до отрисовки и ввода.

Что прозвучало в вашем ответе
  • Отрисовка — между задачами, после очереди микрозадач.
  • then выполняется до отрисовки.
  • Названы requestAnimationFrame с setTimeout или scheduler.yield().
  • Названы разбиение расчёта или Web Worker.

Почитать на сайте

Спецификация: HTML: цикл событий

JavaScript · Junior

Поле количества содержит строку. Почему parseInt("12px") даёт число, а Number("") — ноль? Как принять только целое количество от 1 до 99?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как получить число из input type="number"?
  • Почему isNaN и Number.isNaN ведут себя по-разному?

Разбор

JavaScript
function parseQuantity(raw) {
  const text = raw.trim()
  if (!text) return null
  const value = Number(text)
  return Number.isInteger(value) && value >= 1 && value <= 99
    ? value : null
}

Этот контракт допускает запись 1e1 как 10. Если разрешены только десятичные цифры, до преобразования проверьте /^[0-9]+$/. Не исправляйте дробное количество округлением без согласованного правила продукта.

Уточняющие вопросы

Как получить число из input type="number"?

Через valueAsNumber; пустой или некорректный ввод даёт NaN. Ограничения поля проверяют через checkValidity().

Почему isNaN и Number.isNaN ведут себя по-разному?

Глобальная функция сначала преобразует значение к числу; Number.isNaN проверяет именно числовое значение NaN.

Что прозвучало в вашем ответе
  • Пустой ввод проверен до преобразования.
  • Объяснено чтение префикса через parseInt.
  • Проверены целочисленность и диапазон.

Почитать на сайте

Спецификация: Первоисточник: javascript

JavaScript · Middle и выше

Поле ограничено двадцатью видимыми символами, но эмодзи расходует несколько единиц length. Что считает JavaScript и как считать символы для пользователя?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Исправит ли проблему [...text].length?
  • Для чего может понадобиться normalize("NFC")?

Разбор

JavaScript
const segmenter = new Intl.Segmenter("ru", { granularity: "grapheme" })
const parts = [...segmenter.segment(text)]
const count = parts.length
const preview = parts.slice(0, 20).map(part => part.segment).join("")

Составные эмодзи и буква с комбинируемым ударением показывают разницу между этими единицами. Лимит видимых символов и лимит байтов серверного хранилища — разные ограничения: договоритесь о контракте API и повторите проверку на сервере.

Уточняющие вопросы

Исправит ли проблему [...text].length?

Только для подсчёта кодовых точек. Последовательность из нескольких точек всё ещё может быть одной графемой.

Для чего может понадобиться normalize("NFC")?

Чтобы привести канонически эквивалентные последовательности к общей форме для сравнения. Это не заменяет сегментацию графем.

Что прозвучало в вашем ответе
  • Различены кодовые единицы, кодовые точки и графемы.
  • Предложен Intl.Segmenter с granularity: "grapheme".
  • Согласованы пользовательский лимит и серверная проверка.

Напишите кодом

Спецификация: Первоисточник: javascript

JavaScript · Middle и выше

Функция возвращает save() из try, но её catch не ловит отклонение промиса. Что изменит return await save()?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Нужно ли писать return await в любой обёртке?
  • Что произойдёт, если вернуть значение из finally?

Разбор

JavaScript
async function persist() {
  try {
    return await save()
  } catch (error) {
    throw new Error("Не удалось сохранить", { cause: error })
  } finally {
    setBusy(false)
  }
}

Без await этот catch поймает только синхронную ошибку вызова save(), а finally выполнится до завершения возвращённого промиса. С await очистка происходит после завершения операции. Вызывающий код всё равно должен обработать итоговое отклонение.

Уточняющие вопросы

Нужно ли писать return await в любой обёртке?

Не ради локальной обработки, если её нет. Здесь он нужен для порядка catch и finally.

Что произойдёт, если вернуть значение из finally?

Оно заменит результат или подавит выброшенную ошибку. В очистке обычно не делают return.

Что прозвучало в вашем ответе
  • Различены возврат промиса и ожидание внутри try.
  • Объяснён момент выполнения finally.
  • Ошибка передана вызывающему коду с сохранением причины.

Напишите кодом

Почитать на сайте

Спецификация: Первоисточник: javascript

JavaScript · Junior

При каждом открытии панели добавляют обработчик, а при закрытии вызывают removeEventListener с новой стрелкой. Почему после трёх открытий действие выполняется трижды?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Поможет ли повторный save.bind(model) при снятии?
  • Нужно ли отменять таймеры при закрытии панели?

Разбор

JavaScript
const controller = new AbortController()
button.addEventListener("click", save, { signal: controller.signal })
// При закрытии панели:
controller.abort()

При следующем открытии создают новый контроллер: отменённый сигнал повторно не активируется. При ручном снятии должны совпасть тип события, ссылка на функцию и значение capture. Флаг once удаляет слушатель после первого вызова, но не решает очистку панели, если события не было.

Уточняющие вопросы

Поможет ли повторный save.bind(model) при снятии?

Нет, каждый bind создаёт новую функцию; связанную функцию нужно сохранить заранее.

Нужно ли отменять таймеры при закрытии панели?

Да, если они больше не нужны: сигнал слушателя сам по себе не отменяет setTimeout или setInterval.

Что прозвучало в вашем ответе
  • Объяснена идентичность функции при снятии.
  • Предложено сохранение ссылки или отмена через сигнал.
  • Учтён новый контроллер при повторном открытии.

Напишите кодом

Почитать на сайте

Спецификация: Первоисточник: javascript

TypeScript · Junior

Ответ API приведён к User через as User. Проверит ли TypeScript, что name действительно строка? Как безопаснее принять внешние данные?

                        type User = { name: string };
const data = JSON.parse(source) as User;
console.log(data.name.toUpperCase());
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как написать проверку, после которой TypeScript поверит типу?
  • Чем as отличается от satisfies?

Разбор

as User только сообщает компилятору, каким типом считать выражение. Никакой проверки в браузере не появляется. Примите данные как unknown и проверьте структуру вручную или схемой: объект не null, name является строкой. После успешной проверки можно работать с User. Ошибка проверки должна приводить к понятному состоянию интерфейса, а не к вызову строкового метода у неизвестного значения.

Уточняющие вопросы

Как написать проверку, после которой TypeScript поверит типу?

Функция-предикат: function isUser(x: unknown): x is User { … }. После if (isUser(data)) тип data сужается до User.

Чем as отличается от satisfies?

as заставляет компилятор принять тип. satisfies проверяет, что значение подходит под тип, и при этом сохраняет более точный выведенный тип.

Что прозвучало в вашем ответе
  • Утверждение типа не выполняет проверку во время работы.
  • Внешние данные принимаются как unknown и проверяются до использования.
  • Проверка отличает объект от null и подтверждает нужные свойства.

Спецификация: TypeScript: типы и утверждения типов

TypeScript · Middle и выше

Как написать describe(value: unknown), возвращающую строку, если вход может быть строкой, null или объектом? Почему проверка typeof value === "object" недостаточна?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем unknown лучше any?
  • Что такое never?

Разбор

Сначала обработайте typeof value === "string", затем value === null. В объектной ветке проверьте ненулевое значение и существование нужного свойства, после этого его тип. unknown заставляет явно доказать допустимость операции, тогда как any пропускает её без проверки. Проверка if (value) не годится для всех данных: пустая строка и ноль могут быть корректными значениями.

Уточняющие вопросы

Чем unknown лучше any?

any отключает проверки: с ним можно сделать что угодно, и ошибка всплывёт во время работы. unknown заставляет сузить тип перед использованием.

Что такое never?

Тип значения, которого не бывает. Его используют для проверки полноты switch: если какой-то вариант не обработан, присваивание в never даст ошибку компиляции.

Что прозвучало в вашем ответе
  • unknown требует сужения перед доступом к свойствам.
  • typeof null равен object; null проверяется отдельно.
  • Для свойства проверяется наличие и тип, а не только истинность значения.

Спецификация: TypeScript: сужение типов

TypeScript · Middle и выше

Почему для загрузки данных удобнее объединение ниже, чем объект с optional data и error? Как безопасно прочитать data?

                        type State =
  | { status: "loading" }
  | { status: "success"; data: string[] }
  | { status: "error"; error: string };
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как называется такой приём?
  • Как не забыть обработать новое состояние?

Разбор

После проверки state.status === "success" TypeScript знает, что data существует. Вариант loading не требует фиктивных данных, а error содержит причину ошибки. Это уменьшает число невозможных сочетаний вроде success без data. При расширении объединения можно проверять полноту switch через never. Такая модель описывает состояния, но сама не выполняет запрос и не проверяет ответ сервера.

Уточняющие вопросы

Как называется такой приём?

Размеченное (дискриминированное) объединение: общее поле-литерал, по которому TypeScript различает варианты.

Как не забыть обработать новое состояние?

В конце switch присвоить значение переменной типа never: если добавится вариант, компилятор укажет на необработанный случай.

Что прозвучало в вашем ответе
  • Литерал status различает варианты и сужает тип.
  • data доступно в success, error в error.
  • Нельзя создать противоречивый вариант без обхода проверки типов.

Спецификация: TypeScript: сужение типов

TypeScript · Middle и выше

Почему readonly view меняется после items.push()? Что именно гарантирует readonly и замораживает ли объект?

                        const items = [1];
const view: readonly number[] = items;
items.push(2);
console.log(view);
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем readonly отличается от as const?
  • Как получить неизменяемость во время работы?

Разбор

view показывает тот же массив, поэтому после push содержит оба числа. TypeScript запретил бы view.push(), но не изменение через исходную mutable-ссылку. readonly не выполняет Object.freeze() и не создаёт копию. Даже заморозка объекта в JavaScript по умолчанию поверхностная. Для изоляции выбирайте копирование нужной структуры или дисциплину неизменяемых обновлений.

Уточняющие вопросы

Чем readonly отличается от as const?

as const сохраняет литеральные типы, делает свойства объектного литерала доступными только для чтения, а массивы-литералы — readonly-кортежами. Это не глубокая заморозка: ранее созданный массив в const box = { items } as const по-прежнему можно менять через box.items.push(...).

Как получить неизменяемость во время работы?

Object.freeze — поверхностная заморозка, или копирование при каждом изменении. TypeScript сам этого не делает.

Что прозвучало в вашем ответе
  • readonly ограничивает операции через типизированную ссылку, не замораживая объект.
  • items и view ссылаются на один массив.
  • Глубокая неизменность не возникает автоматически; нужна отдельная стратегия.

Спецификация: TypeScript: свойства объектов и readonly

TypeScript · Middle и выше

Зачем в getProperty связь K extends keyof T? Чем этот вариант полезнее функции с ключом string и результатом any?

                        function getProperty<T, K extends keyof T>(value: T, key: K): T[K] {
  return value[key];
}
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что вернёт keyof для { id: number; title: string }?
  • Когда generic лишний?

Разбор

При getProperty({ count: 3, title: "A" }, "count") результат имеет тип number; ключ "missing" будет ошибкой типов. Generic сохраняет связь между входом, ключом и результатом, которую any стирает. Для объекта из внешнего источника всё равно нужна проверка данных. Обобщение полезно, когда выражает такую связь, а не просто заменяет конкретное имя типа буквой.

Уточняющие вопросы

Что вернёт keyof для { id: number; title: string }?

Объединение литералов 'id' | 'title'.

Когда generic лишний?

Если параметр типа используется один раз и не связывает вход с выходом. Тогда достаточно обычного типа.

Что прозвучало в вашем ответе
  • Ключ ограничен ключами переданного типа.
  • Тип результата T[K] связан с конкретным выбранным ключом.
  • Generic не проверяет произвольный JSON во время выполнения.

Спецификация: TypeScript: обобщённые функции

TypeScript · Junior

Зачем TypeScript, если в браузере всё равно выполняется JavaScript? Какие ошибки он поймает, а какие нет?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делает настройка strict?
  • Какие у TypeScript минусы?

Разбор

TypeScript — это JavaScript с описанием типов. Компилятор проверяет, что код использует значения правильно, и выдаёт обычный JavaScript без типов.

Что он ловит:

  • опечатки: user.nmae вместо user.name;
  • неверные вызовы: строковый метод у числа, функция с недостающим аргументом;
  • забытые проверки: обращение к свойству значения, которое может быть undefined (при включённом strict);
  • последствия изменений: переименовали поле — компилятор покажет все места, которые нужно поправить.

Что не ловит: данные из внешнего мира — ответ API, localStorage, ввод пользователя — приходят уже во время работы, и тип для них — только обещание. Логические ошибки, например сложение вместо вычитания, тоже остаются. Поэтому TypeScript не заменяет проверку данных и тесты.

Уточняющие вопросы

Что делает настройка strict?

Включает строгие проверки, главное — strictNullChecks: null и undefined перестают подходить к любому типу, и компилятор требует их обработать.

Какие у TypeScript минусы?

Нужен шаг сборки, типы приходится писать и поддерживать, сложные типы трудно читать. Для маленького скрипта на одну страницу это может быть лишним.

Что прозвучало в вашем ответе
  • Проверка типов происходит до запуска, после компиляции типов нет.
  • Назван хотя бы один пример ошибки, которую ловит TypeScript.
  • Внешние данные и логические ошибки TypeScript не проверяет.
  • Названа польза для редактора или рефакторинга.

Спецификация: TypeScript Handbook: основы

TypeScript · Junior

Чем interface отличается от type? Что выберете для описания пользователя, а что — для статуса загрузки 'idle' | 'loading' | 'error'?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как описать администратора, у которого есть всё, что у пользователя, плюс список прав?
  • Где пригодится слияние интерфейсов?

Разбор

Пользователя можно описать любым способом:

CSS
interface User { id: number; name: string }
type User = { id: number; name: string }

Отличия:

  • type может назвать что угодно: объединение 'idle' | 'loading' | 'error', кортеж, функцию. interface — только форму объекта;
  • interface расширяют через extends, type — через пересечение &;
  • два interface с одним именем сливаются в один. Это нужно для дополнения чужих типов, но в обычном коде может удивить.

На практике в команде договариваются о правиле, например «объекты — interface, остальное — type», и придерживаются его.

Уточняющие вопросы

Как описать администратора, у которого есть всё, что у пользователя, плюс список прав?

interface Admin extends User { permissions: string[] } или type Admin = User & { permissions: string[] }.

Где пригодится слияние интерфейсов?

Чтобы дописать свойство в чужой тип, например добавить своё поле в глобальный Window.

Что прозвучало в вашем ответе
  • Для объекта подходят оба способа.
  • type описывает объединения и другие типы, interface — только объекты.
  • Названо расширение: extends у интерфейса и & у типа.
  • Статус загрузки описан через type.

Спецификация: TypeScript Handbook: type и interface

TypeScript · Junior

Функция принимает id: string | number. Почему нельзя сразу вызвать id.toUpperCase() и как это исправить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сузить string[] | string?
  • Что такое объединение строковых литералов?

Разбор

string | number значит «или строка, или число». Пока TypeScript не знает, что именно пришло, он разрешает только общие операции, например id.toString().

JavaScript
function format(id: string | number) {
  if (typeof id === 'string') {
    return id.toUpperCase() // здесь id: string
  }
  return id.toFixed(0) // здесь id: number
}

Такие проверки называют сужением типа. Кроме typeof сужают проверка на null, Array.isArray(), оператор in и сравнение общего поля, например state.status === 'error'.

Обходить проверку через id as string плохо: если придёт число, программа упадёт во время работы, а компилятор промолчит.

Уточняющие вопросы

Как сузить string[] | string?

Через Array.isArray(value): в ветке «да» это массив строк, в ветке «нет» — строка.

Что такое объединение строковых литералов?

Тип вроде 'small' | 'medium' | 'large': подходит только одна из этих строк. Опечатку 'midle' компилятор не пропустит.

Что прозвучало в вашем ответе
  • Для объединения доступны только общие операции.
  • Тип сужают проверкой, например typeof id === 'string'.
  • Внутри ветки TypeScript знает точный тип.

Спецификация: TypeScript Handbook: сужение типов

TypeScript · Junior

Есть тип User с полями id, name, email и password. Как без копирования полей получить тип для формы редактирования, где всё необязательно, и тип для ответа API без пароля?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как описать словарь «код страны → название»?
  • Защитит ли Omit от того, что сервер всё-таки пришлёт пароль?

Разбор

Код
type User = { id: number; name: string; email: string; password: string }

type UserEdit = Partial<Omit<User, 'id' | 'password'>>
type PublicUser = Omit<User, 'password'>
type UserPreview = Pick<User, 'id' | 'name'>

Частые утилиты:

  • Partial<T> — все поля необязательные, Required<T> — все обязательные;
  • Pick<T, K> — только перечисленные поля, Omit<T, K> — все, кроме перечисленных;
  • Readonly<T> — поля только для чтения;
  • Record<K, V> — объект с ключами K и значениями V, например словарь.

Главная польза — один источник правды: тип User описан один раз, а производные типы следуют за ним.

Уточняющие вопросы

Как описать словарь «код страны → название»?

Record<string, string>, а если коды известны заранее — Record<'ru' | 'en', string>.

Защитит ли Omit от того, что сервер всё-таки пришлёт пароль?

Нет. Это только описание типа для компилятора; лишнее поле придёт и останется в объекте. Убирать пароль из ответа должен сервер.

Что прозвучало в вашем ответе
  • Назван Partial для необязательных полей.
  • Назван Omit (или Pick) для типа без пароля.
  • Производные типы обновляются вместе с исходным.
  • Утилиты работают только на уровне типов.

Напишите кодом

Спецификация: TypeScript Handbook: утилиты типов

TypeScript · Junior

Как описать типы для функции formatPrice(price, currency), где валюта необязательна, и для колбэка onSelect, который получает выбранный товар и ничего не возвращает?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как типизировать функцию с любым числом аргументов?
  • Чем void отличается от undefined в типе результата?

Разбор

JavaScript
function formatPrice(price: number, currency: string = 'RUB'): string {
  return price.toFixed(2) + ' ' + currency
}

type Props = {
  items: Product[]
  onSelect: (item: Product) => void
}
  • тип параметра пишут после двоеточия, тип результата — после скобок;
  • необязательный параметр — currency?: string (тогда внутри он может быть undefined) или со значением по умолчанию: тип выведется сам;
  • необязательные параметры идут после обязательных;
  • void — результат не используется. Функция, которая что-то возвращает, всё равно подойдёт как колбэк с void;
  • тип результата часто можно не писать — TypeScript выведет его, — но в публичных функциях его указывают явно, чтобы ошибка внутри не изменила контракт незаметно.

Уточняющие вопросы

Как типизировать функцию с любым числом аргументов?

Через rest-параметр: function sum(...nums: number[]): number.

Чем void отличается от undefined в типе результата?

undefined требует вернуть именно undefined. void говорит «результат не важен», поэтому колбэк вроде items.push, который возвращает число, тоже подходит.

Что прозвучало в вашем ответе
  • Типы параметров и результата указаны в объявлении.
  • Необязательный параметр через ? или значение по умолчанию.
  • Колбэк описан типом функции (item: Product) => void.

Спецификация: TypeScript Handbook: функции

TypeScript · Junior

Для статусов заказа можно написать enum Status { New, Paid, Sent } или type Status = 'new' | 'paid' | 'sent'. Чем они отличаются и что выбрать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делает as const?
  • Как проверить, что в switch обработаны все статусы?

Разбор

Объединение литералов — 'new' | 'paid' | 'sent': компилятор не пропустит опечатку, редактор подскажет варианты, а в данных лежат понятные строки. Кода в JavaScript не добавляется.

enum — конструкция, которая превращается в объект:

  • числовой enum по умолчанию нумерует с нуля: в базе и API появляются 0, 1, 2, и перестановка пунктов меняет смысл старых данных;
  • строковый enum Status { New = 'new' } безопаснее, но всё равно добавляет код и требует импорта Status.New вместо строки;
  • часть инструментов, которые просто убирают типы, не поддерживает enum.

Если нужен и тип, и список значений, например для выпадающего списка:

JavaScript
const STATUSES = ['new', 'paid', 'sent'] as const
type Status = (typeof STATUSES)[number] // 'new' | 'paid' | 'sent'

Уточняющие вопросы

Что делает as const?

Делает значения максимально точными: массив становится кортежем только для чтения из конкретных строк, а не string[].

Как проверить, что в switch обработаны все статусы?

В ветке default присвоить значение переменной типа never: новый статус вызовет ошибку компиляции.

Что прозвучало в вашем ответе
  • Объединение литералов существует только в типах.
  • enum создаёт объект в JavaScript.
  • Названа проблема числового enum или лишнего кода.
  • Назван выбор: объединение строк или as const.

Спецификация: TypeScript Handbook: enum

TypeScript · Middle и выше

Напишите тип для функции sortBy(items, key), которая сортирует массив объектов по полю. Как не дать передать несуществующее поле или поле-объект?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему функция копирует массив перед сортировкой?
  • Как сделать порядок по убыванию параметром?

Разбор

JavaScript
function sortBy<T extends Record<K, string | number>, K extends keyof T>(
  items: T[],
  key: K,
): T[] {
  return [...items].sort((a, b) => (a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0))
}

sortBy(products, 'price')   // можно
sortBy(products, 'prise')   // ошибка: нет такого поля
sortBy(products, 'tags')    // ошибка: tags — массив

extends в параметре типа — ограничение: «подойдёт любой тип, который совместим с этим». K extends keyof T связывает ключ с элементом, а T extends Record<K, string | number> требует, чтобы по этому ключу лежало сравнимое значение.

Параметры типа обычно не пишут при вызове: TypeScript выводит их из аргументов. Параметру можно дать значение по умолчанию: <T = unknown>. Хороший признак нужного generic — он связывает вход и выход; если параметр используется один раз, достаточно обычного типа.

Уточняющие вопросы

Почему функция копирует массив перед сортировкой?

sort меняет исходный массив. Копия [...items] или toSorted() не трогает данные вызывающего кода.

Как сделать порядок по убыванию параметром?

Добавить direction: 'asc' | 'desc' = 'asc' и умножать результат сравнения на -1 для desc.

Что прозвучало в вашем ответе
  • Назван K extends keyof T.
  • Ограничено значение по ключу: строки и числа.
  • Объяснено, что параметры типа выводятся из аргументов.

Спецификация: TypeScript Handbook: ограничения generic

TypeScript · Middle и выше

Как устроены встроенные Partial<T> и ReturnType<F>? Напишите свой тип Nullable<T>, где каждое поле может быть null.

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как получить тип элемента массива?
  • Как достать тип значения, которое возвращает промис?

Разбор

JavaScript
type MyPartial<T> = { [K in keyof T]?: T[K] }
type Nullable<T> = { [K in keyof T]: T[K] | null }

type MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never

Сопоставленный тип строит новый объектный тип, перебирая ключи. Модификаторы ? и readonly можно добавлять и снимать: -? делает поля обязательными, так устроен Required. Через as ключи можно переименовать: [K in keyof T as `get${Capitalize<K & string>}`].

Условный тип A extends B ? X : Y выбирает тип по совместимости. infer внутри условия объявляет переменную типа, которую TypeScript находит сам. Распределение по объединению происходит, если слева от extends стоит непосредственно параметр типа, например T extends U ? X : Y: так работает Exclude<'a' | 'b', 'a'>.

Такие типы полезны в библиотеках и общих утилитах. В обычном коде лучше простые явные типы: сложную конструкцию трудно читать и отлаживать.

Уточняющие вопросы

Как получить тип элемента массива?

type Item<A> = A extends readonly (infer U)[] ? U : never. Для индексного доступа требуется ограничение: type Item<A extends readonly unknown[]> = A[number].

Как достать тип значения, которое возвращает промис?

Встроенный Awaited<T>, например Awaited<ReturnType<typeof loadUser>>.

Что прозвучало в вашем ответе
  • Объяснён сопоставленный тип через [K in keyof T].
  • Объяснён условный тип и infer.
  • Написан Nullable<T>.

Спецификация: TypeScript Handbook: сопоставленные типы

TypeScript · Middle и выше

Функция parse(input) возвращает число, если пришла строка, и массив чисел, если пришёл массив строк. Как описать это, чтобы результат вызова имел точный тип?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Проверяет ли TypeScript реализацию на соответствие каждой перегрузке?
  • Почему не parse(input: any): any?

Разбор

JavaScript
function parse(input: string): number
function parse(input: string[]): number[]
function parse(input: string | string[]): number | number[] {
  return Array.isArray(input) ? input.map(Number) : Number(input)
}

const a = parse('5')          // number
const b = parse(['1', '2'])   // number[]

Снаружи видны только сигнатуры перегрузок, реализация должна быть совместима со всеми. TypeScript выбирает первую подходящую перегрузку сверху вниз, поэтому более конкретные пишут раньше.

Тот же результат через generic:

JavaScript
function parse<T extends string | string[]>(input: T): T extends string ? number : number[]

Но внутри такой функции часто нужны приведения типов. Перегрузки проще читать, когда вариантов два-три.

Если тип результата не зависит от входа, перегрузки не нужны: хватит объединения на входе.

Уточняющие вопросы

Проверяет ли TypeScript реализацию на соответствие каждой перегрузке?

Только на совместимость с объединением сигнатур. Вернуть массив для строки компилятор внутри реализации не запретит, поэтому реализацию нужно проверять тестами.

Почему не parse(input: any): any?

Тогда пропадут и проверка аргумента, и тип результата: ошибки всплывут во время работы.

Что прозвучало в вашем ответе
  • Названы сигнатуры перегрузок и одна реализация.
  • Результат вызова получает точный тип.
  • Названа альтернатива: generic с условным типом.

Спецификация: TypeScript Handbook: перегрузки функций

TypeScript · Junior

Нужно ли писать тип у каждой переменной? В каком из примеров TypeScript выведет тип сам, а где его стоит указать явно: const count = 0, let items = [], параметр функции (user) => user.name?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему const status = 'idle' и let status = 'idle' получают разные типы?
  • Что такое «неявный any»?

Разбор

TypeScript выводит типы из значений, и писать их везде не нужно — это шум.

  • const count = 0 — тип 0 (литерал, потому что const), let count = 0 — number. Тип писать не надо;
  • let items = [] — вывод зависит от контекста и настроек: локальный массив может уточняться по добавленным элементам. Чтобы явно задать контракт, напишите: let items: Product[] = [];
  • параметры функции ниоткуда не выводятся: function greet(user: User). Без типа при strict будет ошибка «неявный any»;
  • колбэк получает тип из контекста: в users.map((user) => …) параметр уже User, если users: User[].

Явный тип полезен на границах: параметры, результат экспортируемых функций, данные из API, состояние, которое начинается с null: let selected: User | null = null. Внутри функции доверяйте выводу и смотрите тип наведением мыши в редакторе.

Уточняющие вопросы

Почему const status = 'idle' и let status = 'idle' получают разные типы?

const нельзя переприсвоить, поэтому тип — точный литерал 'idle'. let можно изменить на любую строку, поэтому тип расширяется до string.

Что такое «неявный any»?

Когда тип вывести нельзя, TypeScript подставляет any. Настройка noImplicitAny (часть strict) превращает это в ошибку.

Что прозвучало в вашем ответе
  • Тип переменной с начальным значением выводится сам.
  • Для пустого массива предложен явный тип элементов, если нужен заранее заданный контракт.
  • Параметры функций типизируют явно, колбэки получают тип из контекста.

Спецификация: TypeScript Handbook: вывод типов

TypeScript · Junior

Компилятор пишет: «Object is possibly null» на строке document.querySelector('.menu').classList.add('open'). Что это значит и какие есть способы исправить? Почему ! — не лучший?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как получить элемент сразу нужного типа, например поле ввода?
  • Чем ?? отличается от ||?

Разбор

При включённом strictNullChecks null и undefined не подходят к другим типам, и TypeScript требует разобраться с ними явно.

JavaScript
const menu = document.querySelector('.menu')

if (menu) menu.classList.add('open')          // проверка
menu?.classList.add('open')                   // ничего не делать, если нет
if (!menu) throw new Error('Нет .menu')       // упасть сразу и понятно
document.querySelector('.menu')!.classList   // «точно есть» — без проверки

Какой выбрать — зависит от смысла. Если меню может отсутствовать на части страниц — ?. или if. Если без него код не может работать — проверка с понятной ошибкой: сообщение «Нет .menu» найти легче, чем «Cannot read properties of null».

Оператор ! (non-null assertion) убирает null из типа без проверки. Если элемента не окажется, ошибка всё равно будет — только во время работы и без подсказки.

Уточняющие вопросы

Как получить элемент сразу нужного типа, например поле ввода?

document.querySelector<HTMLInputElement>('#email') задаёт ожидаемый тип, но не проверяет тег во время работы. Для проверки используйте element instanceof HTMLInputElement; она также исключает null.

Чем ?? отличается от ||?

?? подставляет значение только вместо null и undefined, а || — вместо любого ложного, включая 0 и пустую строку.

Что прозвучало в вашем ответе
  • querySelector может вернуть null, компилятор требует это учесть.
  • Названы проверка if, ?. или понятная ошибка.
  • ! ничего не проверяет во время работы.

Почитать на сайте

Спецификация: TypeScript Handbook: null и undefined

TypeScript · Middle и выше

Подключили старую библиотеку на JavaScript, и TypeScript пишет: «Could not find a declaration file for module». Что такое файлы объявлений .d.ts и как решить проблему?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делает skipLibCheck?
  • Как подобрать версию @types к библиотеке?

Разбор

TypeScript проверяет код по типам. Для библиотеки на JavaScript типы берутся из файлов объявлений:

  1. из самого пакета — поле types в его package.json;
  2. из пакета DefinitelyTyped: npm install -D @types/lodash;
  3. из вашего файла, например src/types/old-lib.d.ts:
Код
declare module 'old-lib' {
  export function format(value: number, options?: { currency?: string }): string
}

Описывают только то, что используется, — постепенно. Временное declare module 'old-lib' без тела делает все импорты any: компиляция пройдёт, но проверки нет.

Тем же способом описывают глобальные значения, которые подключены скриптом (declare const ymaps: …), и импорт файлов, которые понимает сборщик: declare module '*.svg' { const url: string; export default url }. Дополнить чужой тип, например Window, можно через слияние интерфейсов в declare global.

Уточняющие вопросы

Что делает skipLibCheck?

skipLibCheck пропускает проверку согласованности всех файлов объявлений .d.ts, в том числе локальных. Типы из них всё ещё используются для проверки вашего кода. Это может скрыть проблемы объявлений, а не исправить их.

Как подобрать версию @types к библиотеке?

Нужна версия типов, совместимая с API установленной библиотеки; номера патчей не обязаны совпадать. Проверьте документацию пакета и используемые функции: старые объявления могут описывать уже удалённые методы.

Что прозвучало в вашем ответе
  • .d.ts описывает типы без кода.
  • Названы типы в пакете или @types.
  • Названо своё объявление declare module.

Спецификация: TypeScript Handbook: файлы объявлений

TypeScript · Middle и выше

Есть объект настроек тем const themes = { light: {…}, dark: {…} }. Как проверить, что каждая тема соответствует типу Theme, и при этом сохранить точные ключи 'light' | 'dark'? Чем satisfies отличается от аннотации типа и as?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Где ещё полезен satisfies?
  • Можно ли satisfies использовать для данных из API?

Разбор

JavaScript
type Theme = { text: string; page: string }

const themes = {
  light: { text: "#222", page: "#fff" },
  dark: { text: "#eee", page: "#1b1d21" },
} satisfies Record<string, Theme>

type ThemeName = keyof typeof themes // "light" | "dark"
// themes.sepia — ошибка: такого ключа нет

const dictionary: Record<string, Theme> = themes
// dictionary.sepia допустим как обращение к ключу словаря

При noUncheckedIndexedAccess результат неизвестного ключа словаря дополнительно включает undefined. Сам словарь не создаёт отсутствующие значения.

as T может скрыть недостающие поля, но компилятор всё же отвергает явно несовместимые утверждения. satisfies T проверяет соответствие без замены результата на общий тип T; контекстная типизация при этом может влиять на вывод. Сохранение всех литералов не гарантируется: для литеральной конфигурации можно использовать as const satisfies Record<string, Theme>. Ни один из этих операторов не проверяет ответ API во время работы.

Уточняющие вопросы

Где ещё полезен satisfies?

В таблицах маршрутов, переводах и конфигурациях: проверить форму и при этом получить из объекта точный тип ключей для автодополнения.

Можно ли satisfies использовать для данных из API?

Нет, он работает только на этапе компиляции для значений в коде. Данные из сети проверяют во время работы.

Что прозвучало в вашем ответе
  • satisfies проверяет соответствие без замены типа объекта общим типом словаря.
  • Аннотация словаря расширяет ключи; утверждение as не заменяет полную проверку структуры.
  • Получены точные ключи через keyof typeof.

Спецификация: TypeScript 4.9: оператор satisfies

TypeScript · Junior

Функция читает users[0].name, но массив иногда пуст. Почему тип User[] сам по себе не гарантирует первый элемент и как защитить обращение?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что будет при чтении неизвестного ключа словаря?
  • Почему одного типа кортежа недостаточно для ответа API?

Разбор

JavaScript
const first = users[0]
if (!first) return "Пользователей нет"
return first.name

Настройка noUncheckedIndexedAccess не входит автоматически в strict. Если непустота действительно гарантирована контрактом, её можно выразить кортежем [User, ...User[]], но входящие данные всё равно проверяют во время работы. users[0]! лишь скрывает потенциальную ошибку от компилятора.

Уточняющие вопросы

Что будет при чтении неизвестного ключа словаря?

Значение может отсутствовать; эта настройка добавит undefined и к чтению через индексную сигнатуру.

Почему одного типа кортежа недостаточно для ответа API?

Тип не проверяет полученный JSON; нужно подтвердить, что массив непустой и его элементы корректны.

Что прозвучало в вашем ответе
  • Названа возможность отсутствующего элемента.
  • Предложена проверка перед обращением к свойству.
  • Названа настройка noUncheckedIndexedAccess и её отличие от strict.

Спецификация: Первоисточник: typescript

TypeScript · Middle и выше

Почему объект с лишним полем иногда принимается функцией, хотя такой же литерал прямо в вызове даёт ошибку? Удаляет ли тип лишние поля?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Сделает ли присваивание const publicUser: Label = user копию?
  • Как гарантировать форму внешнего JSON?

Разбор

JavaScript
type Label = { name: string }
function show(value: Label) { return value.name }
const user = { name: "Ира", password: "secret" }
show(user) // совместим по структуре
// show({ name: "Ира", password: "secret" }) — лишнее поле

Для исходной конфигурации полезен satisfies на литерале. Чтобы отправить только публичные поля, создают новый объект с явным списком свойств; аннотация Label и Omit не очищают данные.

Уточняющие вопросы

Сделает ли присваивание const publicUser: Label = user копию?

Нет, это тот же объект с теми же полями во время работы.

Как гарантировать форму внешнего JSON?

Проверить и преобразовать данные во время работы, например схемой с явным поведением для неизвестных полей.

Что прозвучало в вашем ответе
  • Объяснена структурная совместимость.
  • Отмечена дополнительная проверка свежих литералов.
  • Типизация отличена от удаления полей.

Спецификация: Первоисточник: typescript

TypeScript · Middle и выше

В настройках theme?: string отсутствие свойства означает «сохранить выбор». Чем это отличается от theme: string | undefined и что меняет exactOptionalPropertyTypes?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Включается ли эта настройка вместе с strict?
  • Что сделает JSON.stringify с объектным свойством undefined?

Разбор

Для type Options = { theme?: string } объект {} допустим. При включённой настройке { theme: undefined } уже не подходит. Если оба состояния нужны, пишут theme?: string | undefined.

При чтении необязательного свойства всё равно возможен undefined. Во время работы Object.hasOwn(options, "theme") различает отсутствующий ключ и существующий ключ со значением undefined. Контракт PATCH или функции обновления должен явно определять эти состояния.

Уточняющие вопросы

Включается ли эта настройка вместе с strict?

Нет, её включают отдельно; требуется strictNullChecks.

Что сделает JSON.stringify с объектным свойством undefined?

Пропустит его. Для явного сброса по сети часто используют null, если это предусмотрено контрактом API.

Что прозвучало в вашем ответе
  • Различены отсутствие ключа и значение undefined.
  • Объяснена роль exactOptionalPropertyTypes.
  • Учтено поведение при чтении и передаче в JSON.

Спецификация: Первоисточник: typescript

TypeScript · Junior

В файле нужен только тип User из другого модуля. Зачем писать import type и можно ли затем использовать User в instanceof?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Можно ли импортировать тип класса через import type?
  • Как импортировать модуль ради регистрации обработчиков?

Разбор

Код
import type { User } from "./types"
export function displayName(user: User): string {
  return user.name
}

Если модуль экспортирует класс и код создаёт экземпляр через new или проверяет instanceof, нужен обычный импорт значения. Импорт типа сам по себе не запускает побочные эффекты модуля. При verbatimModuleSyntax различие между импортом типа и значения особенно важно: оно задаётся явно.

Уточняющие вопросы

Можно ли импортировать тип класса через import type?

Да, если класс используется лишь как тип, а не как конструктор или другое значение.

Как импортировать модуль ради регистрации обработчиков?

Явным импортом для побочного эффекта, например import "./register"; импорт типа его не заменяет.

Что прозвучало в вашем ответе
  • Импорт типа удаляется из выходного JavaScript.
  • Для instanceof требуется значение-конструктор.
  • Импорт типа не гарантирует выполнение модуля.

Спецификация: Первоисточник: typescript

Браузер и сеть · Junior

Сервер вернул HTTP 404, но catch не сработал. Почему? Какие ошибки нужно обработать при получении JSON?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сделать тайм-аут для fetch?
  • Почему сервер с ошибкой 500 отдаёт HTML вместо JSON?

Разбор

HTTP-ответ с 404 остаётся ответом, поэтому проверяйте response.ok до обработки ожидаемых данных. Затем await response.json() тоже может завершиться ошибкой. Сетевой отказ и отмену обрабатывают отдельно от HTTP-статуса. Успешный HTTP-ответ ещё не гарантирует правильную структуру JSON: её проверяет приложение.

Уточняющие вопросы

Как сделать тайм-аут для fetch?

Передать signal: AbortSignal.timeout(5000). По истечении времени промис отклонится с ошибкой TimeoutError.

Почему сервер с ошибкой 500 отдаёт HTML вместо JSON?

Страницу ошибки часто формирует веб-сервер или прокси. Поэтому при !response.ok не стоит сразу вызывать json() — можно проверить заголовок Content-Type.

Что прозвучало в вашем ответе
  • fetch не отклоняет Promise только из-за HTTP 404/500.
  • Проверяется response.ok или ожидаемый status.
  • Отдельно возможны сетевой отказ, отмена и ошибка разбора JSON.

Спецификация: WHATWG Fetch: ответы и запросы

Браузер и сеть · Junior

Где в браузере можно хранить данные: localStorage, sessionStorage и cookie? Что выбрать, чтобы запомнить тему оформления, и можно ли хранить там токен авторизации?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как узнать, что значение изменили в другой вкладке?
  • Почему не стоит хранить в cookie много данных?

Разбор

  • localStorage — около 5 МБ на сайт, только строки, сохраняется между сеансами, но может быть удалён пользователем или браузером. Доступен только из JavaScript, на сервер сам не отправляется.
  • sessionStorage — то же, но живёт в пределах одной вкладки и удаляется при её закрытии.
  • cookie — до 4 КБ, браузер отправляет их на сервер с подходящими запросами с учётом домена, пути, Secure, SameSite и режима credentials. Сервер может поставить флаг HttpOnly, и тогда JavaScript их не прочитает.

Объекты сохраняют через JSON.stringify и читают через JSON.parse. Обращения к хранилищу оборачивают в try/catch: в приватном режиме или при заполненной квоте оно может выбросить ошибку.

Тема оформления, свёрнутые панели, черновик — localStorage. Токен авторизации — вопрос безопасности: если на странице есть XSS-уязвимость, чужой скрипт прочитает localStorage. Cookie с HttpOnly скрипт не прочитает, а Secure и SameSite ограничивают, когда она отправляется.

Уточняющие вопросы

Как узнать, что значение изменили в другой вкладке?

Событие storage у window приходит в другие документы того же источника, использующие это хранилище. В документе, который изменил localStorage, оно не срабатывает.

Почему не стоит хранить в cookie много данных?

Каждая cookie отправляется с каждым запросом к сайту, включая картинки и стили. Большие cookie замедляют все запросы.

Что прозвучало в вашем ответе
  • localStorage — бессрочно, sessionStorage — до закрытия вкладки.
  • Cookie отправляются на сервер с каждым запросом.
  • Хранятся строки, объекты через JSON.
  • Тема — в localStorage.
  • Токен в localStorage уязвим при XSS, названа cookie с HttpOnly.

Почитать на сайте

Спецификация: HTML: Web Storage

Браузер и сеть · Middle и выше

Поиск отправил запросы «ко» и «кот». Ответ на «ко» пришёл позже и затёр свежие результаты. Как предотвратить это, включая ошибки и индикатор загрузки?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем debounce отличается от throttle?
  • Как не показать ошибку от отменённого запроса?

Разбор

При новом поиске увеличьте счётчик и запомните его в запросе. Перед записью результата, ошибки и loading сравнивайте его с текущим. Можно дополнительно отменять старый fetch через AbortController, но проверка актуальности защищает и обработку уже полученного ответа. Debounce сокращает число запросов, но не задаёт порядок их завершения. Скрывайте ошибки отменённых поисков, сохраняя ошибки актуального.

Уточняющие вопросы

Чем debounce отличается от throttle?

Debounce вызывает функцию после паузы во вводе, throttle — не чаще заданного интервала. Для поиска при вводе обычно берут debounce.

Как не показать ошибку от отменённого запроса?

Отмена отклоняет fetch с AbortError: в catch его пропускают, а ошибки показывают только для актуального запроса.

Что прозвучало в вашем ответе
  • Каждому поиску назначается версия; DOM обновляет только актуальная версия.
  • Предыдущий запрос можно отменять через AbortController.
  • Проверка актуальности применяется также в catch/finally; debounce сам гонку не устраняет.

Спецификация: WHATWG DOM: отмена операций

Браузер и сеть · Middle и выше

API работает из серверного скрипта, но запрос страницы блокируется CORS. Почему mode: "no-cors" обычно не исправляет получение JSON?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что такое предварительный запрос (preflight)?
  • Как обойти CORS при разработке?

Разбор

Исправьте разрешения CORS на API или используйте доверенный сервер своего приложения. no-cors не открывает доступ к JSON: ответ непрозрачен для JavaScript. Некоторые запросы отправляются даже без разрешения прочитать ответ, а другие требуют предварительной проверки. Поэтому CORS не является общей защитой сервера от отправки запросов.

Уточняющие вопросы

Что такое предварительный запрос (preflight)?

Перед «непростым» запросом — с методом PUT или DELETE, своими заголовками или JSON-телом — браузер отправляет OPTIONS и спрашивает разрешения у сервера.

Как обойти CORS при разработке?

Настроить прокси в dev-сервере, чтобы запросы шли с того же источника, или разрешить нужный origin на сервере API. Отключать проверки в браузере — не решение.

Что прозвучало в вашем ответе
  • CORS регулирует доступ браузерного JavaScript к ответу другого origin.
  • no-cors даёт opaque-ответ без доступного JS тела.
  • Разрешение задаётся сервером; CORS не заменяет авторизацию и защиту от CSRF.

Спецификация: WHATWG Fetch: ответы и запросы

Браузер и сеть · Middle и выше

Чем различаются Cache-Control: no-cache и no-store? Какую роль играют ETag и ответ 304?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как кешировать стили и скрипты надолго, но сразу обновлять при выкладке?
  • Что значит private?

Разбор

no-cache не означает «не сохранять»: сохранённый ответ нужно валидировать перед использованием. С ETag клиент может отправить If-None-Match и получить 304, если представление не изменилось. no-store запрещает хранение ответа; он не является командой удаления всех ранее сохранённых данных. Для персональных ответов важно также различать private и общие кеши.

Уточняющие вопросы

Как кешировать стили и скрипты надолго, но сразу обновлять при выкладке?

Добавлять хеш содержимого в имя файла (app.3f9a.js) и отдавать Cache-Control: max-age=31536000, immutable. Новая версия получает новое имя, а HTML кешируют коротко.

Что значит private?

Ответ можно хранить только в браузере пользователя, но не в общих кешах вроде CDN и прокси. Это нужно для персональных данных.

Что прозвучало в вашем ответе
  • no-cache допускает хранение, но требует проверки перед повторным использованием.
  • no-store запрещает хранить этот ответ в кеше.
  • ETag участвует в условном запросе; 304 позволяет использовать имеющееся тело.

Спецификация: RFC 9111: кеширование HTTP

Браузер и сеть · Middle и выше

Нужно показать имя пользователя из API. Напишите безопасный код вывода обычного текста в элемент nameElement. Объясните, почему innerHTML здесь опасен. Что меняется, если продукту действительно нужна разметка?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем textContent отличается от innerText?
  • Как ещё защититься от XSS?

Разбор

Используйте textContent, если нужна строка имени: символы разметки останутся текстом. innerHTML меняет структуру DOM и создаёт возможность XSS при недоверенном вводе. Если необходим форматированный HTML, применяйте поддерживаемый санитайзер с явной политикой разрешений. Получение строки от своего API не делает её автоматически доверенной: API может возвращать пользовательский контент.

Уточняющие вопросы

Чем textContent отличается от innerText?

textContent просто возвращает и задаёт текст. innerText учитывает CSS: пропускает скрытый текст и вызывает пересчёт раскладки, поэтому медленнее.

Как ещё защититься от XSS?

CSP ограничивает исполнение скриптов согласно явно заданной политике и дополняет безопасный вывод. HttpOnly мешает скрипту прочитать cookie, но не мешает XSS выполнять действия от имени пользователя; это не устранение уязвимости.

Что прозвучало в вашем ответе
  • Недоверенная строка в innerHTML интерпретируется как HTML.
  • Для текста подходит textContent.
  • Для разрешённой разметки нужна проверенная очистка по разрешённому набору, а не самодельная регулярка.

Почитать на сайте

Спецификация: OWASP: предотвращение DOM XSS

Браузер и сеть · Junior

Что такое DOM? Почему во вкладке Elements в DevTools код страницы может отличаться от того, что показывает «Просмотр кода страницы»?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем атрибут отличается от свойства DOM?
  • Почему частые изменения DOM могут тормозить страницу?

Разбор

Браузер читает HTML и превращает каждый тег в объект-узел: так получается дерево, где у <html> есть дети <head> и <body>, у них — свои дети и так далее. Это и есть DOM (Document Object Model). Через него JavaScript находит элементы (document.querySelector), меняет текст и атрибуты, добавляет и удаляет узлы.

Почему Elements отличается от исходника:

  • браузер исправляет ошибки: закрывает незакрытые теги, добавляет <tbody> в таблицу, переносит неверно вложенные элементы;
  • скрипты меняют страницу после загрузки: подгружают список, открывают меню, добавляют классы;
  • значения, введённые в поля, хранятся в свойствах DOM, а не в атрибутах исходного HTML.

Уточняющие вопросы

Чем атрибут отличается от свойства DOM?

Атрибут записан в HTML, свойство — у объекта в JavaScript. Для поля ввода атрибут value — начальное значение, а свойство input.value — то, что сейчас введено.

Почему частые изменения DOM могут тормозить страницу?

После изменения браузеру может понадобиться пересчитать раскладку и перерисовать часть страницы. Много мелких изменений подряд лучше собрать в одно.

Что прозвучало в вашем ответе
  • DOM — дерево объектов, построенное браузером из HTML.
  • JavaScript читает и меняет страницу через DOM.
  • Elements показывает текущий DOM, «Просмотр кода» — исходный HTML.
  • Названа хотя бы одна причина различий: исправление ошибок или изменения скриптом.

Почитать на сайте

Спецификация: DOM Standard

Браузер и сеть · Junior

Как браузер превращает HTML и CSS в картинку на экране? Почему изменение width дороже для браузера, чем изменение transform?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему стили подключают в <head>?
  • Что такое принудительная раскладка?

Разбор

Путь от кода до пикселей:

  1. HTML → дерево DOM, CSS → дерево стилей CSSOM;
  2. вычисление стилей: какие правила применяются к каждому элементу;
  3. layout — расчёт размеров и положения;
  4. paint — рисование текста, фона, рамок, теней;
  5. composite — сборка нарисованных слоёв в итоговую картинку.

Если меняется геометрия — width, height, margin, размер шрифта, — браузер пересчитывает раскладку, и это может затронуть соседей и всю страницу. Изменение цвета или фона требует только перерисовки. transform и opacity браузер часто применяет на этапе сборки, поэтому их и берут для плавной анимации.

Уточняющие вопросы

Почему стили подключают в <head>?

Без стилей браузер не может правильно нарисовать страницу. Если стили придут позже, пользователь на мгновение увидит голый HTML, а потом страница «прыгнет».

Что такое принудительная раскладка?

Если скрипт меняет стили и тут же читает размеры, например offsetHeight, браузеру приходится сразу пересчитать раскладку. В цикле это сильно тормозит.

Что прозвучало в вашем ответе
  • Названы DOM и стили (CSSOM) как исходные данные.
  • Названы этапы layout, paint и composite.
  • Изменение геометрии запускает раскладку заново.
  • transform и opacity дешевле для анимации.

Почитать на сайте

Спецификация: HTML: цикл событий и отрисовка

Браузер и сеть · Junior

Пользователь пишет: «Нажимаю кнопку „Сохранить“, ничего не происходит». С чего начнёте поиск причины в браузере?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делать, если ошибка возникает только у пользователя, а у вас нет?
  • Почему «ничего не происходит» — само по себе проблема интерфейса?

Разбор

  1. Воспроизвести. Уточнить браузер, страницу, действия. Без воспроизведения легко чинить не то.
  2. Console. Красные ошибки часто сразу показывают файл и строку: обработчик упал до отправки.
  3. Network. Ушёл ли запрос после нажатия? Если ушёл — какой код ответа и что в теле: 400 с ошибкой проверки, 500, ответ не тот, что ждёт код.
  4. Elements. Кнопка ли это вообще, не перекрыта ли она прозрачным элементом, не стоит ли disabled. В разделе Event Listeners видно, есть ли обработчик клика.
  5. Сузить. Поставить точку останова в обработчике во вкладке Sources или временный console.log и пройти код по шагам.

После исправления стоит проверить исходный сценарий ещё раз и подумать, как не допустить такой ошибки снова: например, показывать пользователю сообщение, если запрос не удался.

Уточняющие вопросы

Что делать, если ошибка возникает только у пользователя, а у вас нет?

Сравнить окружение: браузер и его версию, расширения, размер экрана, данные аккаунта, медленную сеть. В DevTools можно включить замедление сети и эмуляцию устройства.

Почему «ничего не происходит» — само по себе проблема интерфейса?

Пользователь должен видеть результат действия: загрузку, успех или ошибку. Если запрос упал молча, человек не понимает, сохранились ли данные.

Что прозвучало в вашем ответе
  • Сначала воспроизвести проблему.
  • Названа вкладка Console и поиск ошибок.
  • Названа вкладка Network: ушёл ли запрос и что ответил сервер.
  • Названы проверка элемента или обработчика и точка останова.

Почитать на сайте

Спецификация: Chrome DevTools: отладка JavaScript

Браузер и сеть · Middle и выше

Что такое Service Worker и что он даёт сайту? Как сделать, чтобы прочитанная статья открывалась без интернета, и почему после выкладки пользователи могут видеть старую версию?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем Cache API отличается от HTTP-кеша?
  • Может ли Service Worker менять DOM?

Разбор

Воркер регистрируют из страницы: navigator.serviceWorker.register('/sw.js'). Работает только на HTTPS (или localhost) и только для своего пути.

Жизненный цикл:

  1. install — сохранить в кеш нужное для запуска: каркас страницы, стили, скрипты;
  2. activate — удалить старые кеши;
  3. fetch — решать, откуда брать ответ.

Стратегии для fetch:

  • сначала кеш — для файлов с хешем в имени, которые не меняются;
  • сначала сеть, при ошибке — кеш — для статей: свежая версия онлайн, сохранённая офлайн;
  • кеш сразу, обновить в фоне (stale-while-revalidate) — быстро и почти свежо.

Для статьи: при открытии класть ответ в кеш, без сети отдавать его, а для непрочитанных — страницу «Нет соединения».

Обновление: новый воркер устанавливается, но ждёт (waiting), пока открыта хоть одна вкладка со старым. skipWaiting() ускоряет смену, но тогда старая страница может получить файлы новой версии. Часто показывают «Доступна новая версия — обновить».

Уточняющие вопросы

Чем Cache API отличается от HTTP-кеша?

HTTP-кешем управляют заголовки и браузер. Cache API — хранилище, которым управляет ваш код: что сохранить, когда отдать и когда удалить.

Может ли Service Worker менять DOM?

Нет, у него нет доступа к странице. Он общается с ней сообщениями через postMessage.

Что прозвучало в вашем ответе
  • Воркер работает отдельно от страницы и перехватывает запросы.
  • Названы события install, activate, fetch или Cache API.
  • Названа стратегия для офлайн-статей.
  • Объяснено ожидание новой версии и способ обновления.

Спецификация: Service Workers

Браузер и сеть · Middle и выше

Что такое CSRF и кликджекинг? Как злоумышленник может заставить вошедшего пользователя перевести деньги и что защищает от этого на стороне браузера и сервера?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем атрибут sandbox у <iframe>?
  • Нужен ли CSRF-токен, если API принимает только JSON с токеном в заголовке Authorization?

Разбор

CSRF. Пользователь вошёл в банк. На чужой странице есть форма, которая автоматически отправляет POST на адрес перевода. Если cookie сессии уходит с этим запросом, сервер считает его настоящим.

  • SameSite=Lax (по умолчанию во многих браузерах) не отправляет cookie с чужих сайтов в POST-запросах, Strict — вообще ни в каких;
  • CSRF-токен — случайное значение в форме, которое чужой сайт не может прочитать;
  • проверка заголовков Origin или Sec-Fetch-Site на сервере;
  • действия только через POST и другие небезопасные методы, никогда через GET.

Кликджекинг. Злоумышленник встраивает ваш сайт во фрейм с opacity: 0 и ставит под кнопку «Перевести» свою кнопку «Получить приз». Защита — запретить встраивание: frame-ancestors в CSP или старый заголовок X-Frame-Options. Для критичных действий помогает подтверждение, которое сложно «прокликать».

CORS от этого не защищает: он ограничивает чтение ответа, а не отправку запроса.

Уточняющие вопросы

Зачем атрибут sandbox у <iframe>?

Он защищает вашу страницу от встроенной: запрещает чужому фрейму скрипты, формы, всплывающие окна и переход верхней страницы, если явно не разрешить.

Нужен ли CSRF-токен, если API принимает только JSON с токеном в заголовке Authorization?

Обычно нет: чужой сайт не может добавить такой заголовок без разрешения CORS, а браузер сам его не прикладывает, как cookie.

Что прозвучало в вашем ответе
  • Объяснён CSRF: браузер прикладывает cookie к запросу с чужого сайта.
  • Названы SameSite и CSRF-токен или проверка Origin.
  • Объяснён кликджекинг через невидимый фрейм.
  • Названы frame-ancestors или X-Frame-Options.

Почитать на сайте

Спецификация: OWASP: защита от CSRF

Браузер и сеть · Junior

Чем событие DOMContentLoaded отличается от load? Скрипт в <head> вызывает document.querySelector('.menu') и получает null — почему и как исправить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем async отличается от defer?
  • Что случится, если ждать load для запуска всего интерфейса?

Разбор

Браузер читает HTML сверху вниз. Обычный <script> останавливает разбор и выполняется сразу — элементов ниже него ещё нет.

  • DOMContentLoaded — документ разобран, все элементы есть, отложенные скрипты выполнены. Картинки могут ещё грузиться;
  • load у window — завершилась загрузка ресурсов, задерживающих это событие, например обычных картинок, стилей и фреймов. Ленивые ресурсы и будущие запросы скрипта оно не ждёт. Нужен редко, например чтобы узнать настоящие размеры картинки.

Как исправить:

  • <script src="app.js" defer> — скачивается параллельно и выполняется после разбора HTML, по порядку. Лучший вариант для большинства скриптов;
  • type="module" ведёт себя как defer;
  • скрипт в конце <body> — старый, но рабочий способ;
  • обернуть код в обработчик DOMContentLoaded — если скрипт нельзя перенести.

Уточняющие вопросы

Чем async отличается от defer?

async выполняется сразу, как только скачался, в любом порядке и может прервать разбор. Подходит для независимых скриптов вроде счётчика посещений.

Что случится, если ждать load для запуска всего интерфейса?

Меню и кнопки заработают только после загрузки всех картинок: на медленной сети страница долго будет «мёртвой».

Что прозвучало в вашем ответе
  • DOMContentLoaded — DOM готов, load — загружено всё.
  • Скрипт выполняется раньше, чем браузер дошёл до элемента.
  • Названо исправление: defer, type="module" или скрипт в конце <body>.

Почитать на сайте

Спецификация: HTML: конец разбора документа

Браузер и сеть · Junior

Что такое источник (origin) и правило одного источника? Совпадают ли источники у https://shop.ru, http://shop.ru, https://api.shop.ru и https://shop.ru:8080?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему фронтенд на localhost:5173 не может обратиться к API на localhost:3000?
  • Чем «сайт» в SameSite отличается от источника?

Разбор

Источник = протокол + домен + порт. Путь не важен: https://shop.ru/cart и https://shop.ru/about — один источник. А вот:

  • http://shop.ru — другой протокол;
  • https://api.shop.ru — другой домен, даже поддомен;
  • https://shop.ru:8080 — другой порт.

Правило одного источника — базовая защита браузера. Без него любой открытый сайт мог бы прочитать вашу почту в соседней вкладке. Ограничения:

  • для межсайтового fetch в режиме CORS чтение ответа требует разрешения сервера. Если нужен preflight и он не прошёл, основной запрос вообще не отправится;
  • localStorage, IndexedDB — свои у каждого источника;
  • в чужой <iframe> нельзя заглянуть скриптом, общаться можно через postMessage.

Cookie живут по своим, более старым правилам: они привязаны к домену и пути, а порт не различают. Поэтому https://shop.ru и https://shop.ru:8080 видят одни и те же cookie, а cookie с Domain=shop.ru доступна и api.shop.ru.

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

Уточняющие вопросы

Почему фронтенд на localhost:5173 не может обратиться к API на localhost:3000?

Разные порты — разные источники. Нужен CORS на API или прокси в dev-сервере, чтобы запросы шли с того же источника.

Чем «сайт» в SameSite отличается от источника?

В SameSite сравнивают схему и регистрируемый домен с учётом Public Suffix List, а не просто последние две части имени. Например, https://api.shop.co.uk и https://shop.co.uk — один сайт, но разные источники. Порты при сравнении сайтов не учитываются.

Что прозвучало в вашем ответе
  • Источник — протокол, домен и порт.
  • Все четыре адреса — разные источники, объяснено почему.
  • Названо, что именно ограничено: чтение ответов, хранилища, фреймы.
  • Названы CORS или postMessage как разрешённые способы.

Спецификация: HTML: источники

Браузер и сеть · Junior

Как пользоваться вкладкой Network в DevTools? Как найти запрос, который вернул ошибку, посмотреть, что ушло на сервер, и проверить страницу на медленном интернете?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему запрос отменён (canceled)?
  • Что значит «(disk cache)» в колонке размера?

Разбор

  • Записать. Вкладка записывает только при открытых DevTools. Открыть её и повторить действие или обновить страницу.
  • Найти. Фильтры по типу: Fetch/XHR — запросы скриптов к API, Doc, CSS, JS, Img. Поиск по адресу. Ошибочные запросы (4xx, 5xx, сеть) выделены красным.
  • Разобрать запрос. Headers — адрес, метод, код ответа и заголовки; Payload — тело запроса и параметры; Preview и Response — ответ сервера; Initiator — какой код его отправил; Timing — DNS, соединение, ожидание и загрузка.
  • Условия. Throttling — «Slow 4G» или «Offline»; Disable cache — как у пользователя, который пришёл впервые; Preserve log — не очищать список при переходе на другую страницу.

Запрос можно повторить: правой кнопкой — Copy as fetch или Copy as cURL. Это удобно, чтобы передать проблему бэкенд-разработчику.

Уточняющие вопросы

Почему запрос отменён (canceled)?

Его прервали: страница ушла на другой адрес, сработал AbortController или браузер заменил запрос новым.

Что значит «(disk cache)» в колонке размера?

Ответ взят из HTTP-кеша браузера, без обращения к серверу.

Что прозвучало в вашем ответе
  • Названы фильтры и поиск ошибочного запроса.
  • Названы Headers, Payload и Response.
  • Названы Throttling или Disable cache.

Спецификация: Chrome DevTools: вкладка Network

Браузер и сеть · Junior

Каталог должен сохранять фильтр в адресе: /catalog?color=red&page=2, чтобы ссылкой можно было поделиться. Как прочитать и изменить такие параметры в JavaScript?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем параметры адреса отличаются от части после #?
  • Что делать с неправильным значением, например page=abc?

Разбор

JavaScript
const url = new URL(location.href)
const color = url.searchParams.get('color')        // 'red' или null
const page = Number(url.searchParams.get('page') ?? 1)

url.searchParams.set('color', 'blue')
url.searchParams.delete('page')
history.replaceState(null, '', url)               // адрес обновился без перезагрузки
  • все значения — строки: числа нужно преобразовать и проверить;
  • URLSearchParams сам кодирует пробелы и кириллицу — не собирайте адрес склейкой строк;
  • повторяющийся параметр ?tag=html&tag=css читается через getAll('tag');
  • replaceState меняет текущую запись истории, pushState добавляет новую. Для фильтров чаще replaceState, чтобы «Назад» не листал каждый щелчок по фильтру;
  • при pushState нужно слушать событие popstate и перерисовывать каталог при переходе назад.

Уточняющие вопросы

Чем параметры адреса отличаются от части после #?

Параметры уходят на сервер, а фрагмент #… остаётся в браузере. Фрагмент обычно ведёт на место на странице.

Что делать с неправильным значением, например page=abc?

Проверить и подставить значение по умолчанию: Number.isInteger(page) && page > 0 ? page : 1.

Что прозвучало в вашем ответе
  • Названы URL и URLSearchParams с get и set.
  • Адрес обновляется через history.replaceState или pushState.
  • Значения — строки, их проверяют.

Почитать на сайте

Спецификация: URL Standard

Браузер и сеть · Middle и выше

Фильтрация и сортировка 100 000 строк таблицы подвешивают страницу на секунду. Как перенести работу в Web Worker и что в нём недоступно?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем Web Worker отличается от Service Worker?
  • Как отменить устаревший расчёт, если пользователь продолжает печатать?

Разбор

JavaScript
// main.js
const worker = new Worker(new URL('./filter.js', import.meta.url), { type: 'module' })
worker.postMessage({ rows, query })
worker.onmessage = (event) => renderTable(event.data)

// filter.js
self.onmessage = ({ data }) => {
  const result = data.rows.filter((row) => row.name.includes(data.query))
  self.postMessage(result)
}
  • воркер — отдельный поток со своим циклом событий. Долгий расчёт в нём не блокирует клики и анимации;
  • сообщения копируются алгоритмом structured clone. Для очень больших данных — передача ArrayBuffer во втором аргументе postMessage без копирования;
  • доступны fetch, таймеры, IndexedDB, OffscreenCanvas; недоступны DOM, document, window, localStorage;
  • создание воркера и копирование данных стоят времени — для расчёта на 5 мс выигрыша не будет.

Если данные таблицы нужны постоянно, их загружают и держат в воркере, а в сообщениях передают только запрос и видимую часть результата. Дополнительно помогает виртуализация: рисовать в DOM только видимые строки.

Уточняющие вопросы

Чем Web Worker отличается от Service Worker?

Web Worker — помощник конкретной страницы для вычислений, живёт, пока жива страница. Service Worker — посредник между сайтом и сетью, общий для всех вкладок, живёт отдельно и может работать без открытой страницы.

Как отменить устаревший расчёт, если пользователь продолжает печатать?

Нумеровать запросы и отбрасывать устаревшие ответы или вызывать worker.terminate() и создавать новый воркер.

Что прозвучало в вашем ответе
  • Названы new Worker, postMessage и onmessage.
  • Основной поток остаётся свободным.
  • Названо, что недоступно: DOM, window, localStorage.
  • Данные копируются; названа передача буферов или стоимость копирования.

Спецификация: HTML: Web Workers

Браузер и сеть · Middle и выше

Нужно подгружать следующую страницу ленты, когда пользователь докрутил до конца, и запускать анимацию блока при его появлении. Почему обработчик scroll с getBoundingClientRect() — плохой путь и чем его заменить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как не загрузить одну страницу дважды?
  • Нужен ли IntersectionObserver для ленивых картинок?

Разбор

JavaScript
const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) loadNextPage()
  }
}, { rootMargin: '400px' }) // начать загрузку заранее

observer.observe(document.querySelector('.feed-end'))
  • браузер вычисляет пересечения сам, без пересчёта раскладки на каждом событии прокрутки;
  • rootMargin расширяет зону — следующая страница начнёт грузиться за 400px до конца;
  • threshold — какая доля элемента должна стать видимой: 0.5 — половина;
  • после срабатывания одноразового наблюдения вызывают observer.unobserve(element).

ResizeObserver сообщает об изменении размера элемента, а не окна — удобно для графиков и компонентов в колонках. MutationObserver отслеживает изменения DOM.

Для бесконечной ленты нужна и доступность: кнопка «Показать ещё» как запасной вариант, сохранение позиции при возврате назад и доступный подвал, до которого иначе не добраться.

Уточняющие вопросы

Как не загрузить одну страницу дважды?

Флаг «идёт загрузка» и номер следующей страницы. Пока загрузка идёт, новые срабатывания игнорируют.

Нужен ли IntersectionObserver для ленивых картинок?

Обычно нет: loading="lazy" делает это встроенно. Наблюдатель нужен для своей логики.

Что прозвучало в вашем ответе
  • Названа проблема scroll и принудительной раскладки.
  • Назван IntersectionObserver и наблюдение за маячком в конце.
  • Названы rootMargin или threshold.
  • Назван ResizeObserver или доступность ленты.

Спецификация: Intersection Observer

Браузер и сеть · Junior

После чтения сохранённого черновика через JSON.parse(localStorage.getItem("draft")) страница иногда падает. Какие ошибки возможны и как сохранить работоспособность формы?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Достаточно ли успешного JSON.parse?
  • Что делать с записью старой версии?

Разбор

Обрабатывайте отдельно отсутствие записи, синтаксическую ошибку JSON и неподходящую структуру. У записи полезно хранить версию формата: старую версию преобразуют либо отклоняют. try/catch нужен и вокруг записи — возможна нехватка квоты.

При невозможности сохранения форма продолжает работать в памяти и сообщает, что черновик не сохранён. Не показывайте «Сохранено» до успешного setItem и не стирайте остальные настройки через localStorage.clear().

Уточняющие вопросы

Достаточно ли успешного JSON.parse?

Нет: результат может быть числом, null или объектом с другими полями. Нужно проверить форму и допустимые значения.

Что делать с записью старой версии?

Применить известное преобразование или сбросить только эту запись; не угадывать структуру.

Что прозвучало в вашем ответе
  • Учтены недоступность хранилища и ошибки JSON.
  • После разбора проверены структура и версия.
  • Форма работает без сохранения и честно сообщает результат.

Почитать на сайте

Спецификация: Первоисточник: browser

Браузер и сеть · Middle и выше

Встроенный виджет присылает сообщения через postMessage. Почему опасно сразу выполнять действие по event.data и как проверить отправителя?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем проверять источник окна, если домен совпал?
  • Можно ли использовать "*" для секретного ответа?

Разбор

JavaScript
window.addEventListener("message", (event) => {
  if (event.origin !== "https://widget.example") return
  if (event.source !== frame.contentWindow) return
  const data = event.data
  if (!data || typeof data !== "object" || data.type !== "resize") return
  if (!Number.isFinite(data.height) || data.height < 100 || data.height > 1000) return
  frame.style.height = data.height + "px"
})

Проверка по подстроке адреса ненадёжна: похожее имя домена может принадлежать другому владельцу. Сообщение — данные, а не исполняемый код. Даже доверенный отправитель может ошибиться, поэтому значения ограничиваются контрактом.

Уточняющие вопросы

Зачем проверять источник окна, если домен совпал?

Сообщение могло прийти из другого окна того же источника; проверка связывает его с конкретным виджетом.

Можно ли использовать "*" для секретного ответа?

Нет: окно могло перейти на другой источник. Точный targetOrigin предотвращает доставку неподходящему получателю.

Что прозвучало в вашем ответе
  • Источник сравнивается точно, без подстрок.
  • Проверен event.source.
  • Данные проверены по типам и допустимым значениям.
  • При отправке указан конкретный targetOrigin.

Спецификация: Первоисточник: browser

Браузер и сеть · Middle и выше

Офлайн-каталог хранит тысячи товаров и изображения. Почему localStorage неудобен и какие гарантии даёт транзакция IndexedDB?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Когда менять схему хранилища?
  • Можно ли обещать вечное хранение офлайн-данных?

Разбор

localStorage синхронен и хранит строки; большие сериализации и записи мешают интерфейсу. В IndexedDB можно хранить записи и Blob, выбирать данные по ключам и индексам.

Связанное обновление товара и служебной версии выполняют в одной readwrite-транзакции нужных хранилищ. Об успехе сообщают после complete, об отмене — по abort. Сеть обычно завершают до открытия транзакции: ожидание произвольного промиса может оставить её без работы и привести к завершению раньше следующего запроса. Квоты и удаление данных браузером всё равно возможны.

Уточняющие вопросы

Когда менять схему хранилища?

В транзакции обновления версии, открываемой через событие upgradeneeded; изменение структуры нужно планировать как миграцию.

Можно ли обещать вечное хранение офлайн-данных?

Нет: пользователь или браузер могут удалить данные. Важные записи синхронизируют или дают экспортировать.

Что прозвучало в вашем ответе
  • Названы асинхронность и структурированное хранение.
  • Успех запроса отличён от завершения транзакции.
  • Сетевое ожидание вынесено за пределы транзакции.

Почитать на сайте

Спецификация: Первоисточник: browser

Браузер и сеть · Middle и выше

После нажатия «Назад» страница мгновенно появляется со старым состоянием, а DOMContentLoaded не вызывается повторно. Как это объяснить и что обновить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем это отличается от ответа из HTTP-кеша?
  • Стоит ли полагаться на unload для сохранения черновика?

Разбор

JavaScript
window.addEventListener("pageshow", (event) => {
  if (event.persisted) refreshSessionAndCounters()
})

Обновляйте данные, которые могли устареть: состояние входа, корзину, доступность действий. Не сбрасывайте без необходимости введённый текст и положение прокрутки. Инициализация должна быть идемпотентной: повторное добавление обработчиков приведёт к дублированию действий. BFCache не является обычным HTTP-кешем файлов.

Уточняющие вопросы

Чем это отличается от ответа из HTTP-кеша?

HTTP-кеш хранит ответы на запросы, а BFCache восстанавливает состояние документа и JavaScript.

Стоит ли полагаться на unload для сохранения черновика?

Нет: событие ненадёжно. Черновик сохраняют по изменениям; жизненный цикл страницы учитывают дополнительно.

Что прозвучало в вашем ответе
  • Названо восстановление полного состояния страницы.
  • Выбрано событие pageshow и проверка persisted.
  • Обновлены устаревающие данные без потери пользовательского ввода.

Спецификация: Первоисточник: browser

Браузер и сеть · Junior

Простой GET к API с другого домена работает, а PUT с заголовком Content-Type: application/json падает с ошибкой CORS. Во вкладке Network виден запрос OPTIONS. Что это за запрос и что должен ответить сервер?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что нужно для отправки cookie в межсайтовом запросе?
  • Защищает ли CORS сервер от запросов?

Разбор

Простыми считаются запросы GET, HEAD, POST со стандартными заголовками и Content-Type из трёх вариантов (форма, multipart, text/plain). Всё остальное — другие методы, application/json, свои заголовки вроде Authorization — требует preflight.

Браузер отправляет OPTIONS с заголовками Origin, Access-Control-Request-Method и Access-Control-Request-Headers. Сервер отвечает, например:

  • Access-Control-Allow-Origin: https://shop.ru;
  • Access-Control-Allow-Methods: GET, PUT, DELETE;
  • Access-Control-Allow-Headers: Content-Type, Authorization;
  • Access-Control-Max-Age: 600 — сколько секунд браузер может помнить разрешение.

Исправляют на сервере, а не во фронтенде. Частая причина — сервер требует авторизацию для OPTIONS или не обрабатывает этот метод вовсе.

Уточняющие вопросы

Что нужно для отправки cookie в межсайтовом запросе?

На клиенте credentials: 'include', на сервере Access-Control-Allow-Credentials: true и конкретный источник вместо *.

Защищает ли CORS сервер от запросов?

Нет: это правило браузера о чтении ответа. curl или серверный скрипт отправят любой запрос, поэтому авторизацию проверяет сервер.

Что прозвучало в вашем ответе
  • Объяснено, что такое preflight и когда он нужен.
  • Названы заголовки ответа сервера.
  • Сказано, что без разрешения основной запрос не уйдёт.

Спецификация: Fetch: CORS protocol

Сети и HTTP · Junior

После смены IP сайта часть пользователей попадает на старый сервер. Как DNS и TTL могут это объяснить и что проверить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как подготовить переезд на новый сервер?
  • Чем запись A отличается от CNAME?

Разбор

Смена записи не очищает уже существующие кеши. Резолвер может вернуть прежний адрес, пока не истечёт срок его хранения. Проверьте авторитетные ответы и ответы используемого резолвера, цепочку псевдонимов и TTL. Очистка локального кеша помогает диагностике, но не обновляет кеши остальных пользователей. Неверные записи тоже возможны: ждать истечения TTL без проверки конфигурации недостаточно.

Уточняющие вопросы

Как подготовить переезд на новый сервер?

Заранее снизить TTL записи, например до 300 секунд, дождаться истечения старого TTL, переключить адрес и какое-то время держать оба сервера.

Чем запись A отличается от CNAME?

A указывает на IPv4-адрес, AAAA — на IPv6, CNAME — на другое имя, адрес которого нужно узнать отдельно.

Что прозвучало в вашем ответе
  • DNS сопоставляет имя с записями, включая адреса; это не HTTP-перенаправление.
  • Кешированные ответы могут действовать до истечения TTL.
  • Нужно сравнить ответы резолверов и авторитетного сервера, проверить записи и их TTL.

Спецификация: RFC 1034: DNS и кеширование

Сети и HTTP · Junior

Сайт использует HTTPS. Что это защищает и означает ли это, что самому сайту можно доверять любые данные?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что проверяет браузер в сертификате?
  • Что такое HSTS?

Разбор

TLS защищает обмен от чтения и подмены на пути между участниками соединения. Браузер проверяет сертификат сервера, включая соответствие имени. Ошибка сертификата требует выяснения причины, а не отключения проверки. Сайт с корректным HTTPS всё ещё может быть мошенническим, содержать XSS или небезопасно хранить полученные сведения. Шифрование канала не заменяет защиту приложения.

Уточняющие вопросы

Что проверяет браузер в сертификате?

Что он выдан доверенным центром, не просрочен, не отозван и выдан именно для этого домена.

Что такое HSTS?

Заголовок, который велит браузеру всегда открывать сайт по HTTPS, даже если пользователь ввёл адрес с http.

Что прозвучало в вашем ответе
  • TLS защищает конфиденциальность и целостность передаваемых данных.
  • Проверка сертификата связывает соединение с ожидаемым сервером.
  • HTTPS не гарантирует добросовестность сайта и безопасность данных на конечных узлах.

Спецификация: RFC 8446: TLS 1.3

Сети и HTTP · Middle и выше

Первое открытие HTTPS-сайта медленное, повторное быстрее. Какие сетевые этапы стоит проверить, прежде чем оптимизировать JavaScript?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как ускорить соединение со сторонним доменом?
  • Что входит в TTFB?

Разбор

В Network проверьте DNS, установку соединения и TLS, ожидание первого байта и загрузку ответа. При повторном открытии могут использоваться кеш и готовое соединение. Не требуйте одинаковой последовательности для каждого запроса: она зависит от протокола и состояния браузера. Высокий TTFB сам по себе не доказывает медленную базу данных: в него входят сетевое ожидание и подготовка ответа сервером.

Уточняющие вопросы

Как ускорить соединение со сторонним доменом?

<link rel="preconnect" href="https://cdn.example.com"> заранее выполняет DNS, TCP и TLS.

Что входит в TTFB?

Время от отправки запроса до первого байта ответа: сеть в обе стороны и подготовка ответа сервером.

Что прозвучало в вашем ответе
  • Различены поиск адреса, установка соединения, TLS и обмен HTTP.
  • Учтены кеши и повторное использование соединения: этапы не всегда выполняются заново.
  • Проверяются Timing и Waterfall, а не только общая длительность.

Спецификация: Chrome DevTools: время сетевых запросов

Сети и HTTP · Middle и выше

Почему утверждение «HTTP всегда работает поверх TCP» неверно? Чем HTTP/2 и HTTP/3 различаются при потере пакета?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что дал HTTP/2 по сравнению с HTTP/1.1?
  • Ненадёжен ли HTTP/3, раз UDP не гарантирует доставку?

Разбор

HTTP/2 мультиплексирует запросы в одном TCP-соединении, но пропуск в последовательности TCP задерживает выдачу последующих байтов. HTTP/3 использует QUIC: потеря данных одного потока не требует остановить доставку данных остальных потоков. Это не обещание, что потери совсем не влияют на другие запросы: ресурсы и управление перегрузкой остаются общими. Версия HTTP меняет транспорт, но сохраняет смысл методов и статусов.

Уточняющие вопросы

Что дал HTTP/2 по сравнению с HTTP/1.1?

Мультиплексирование многих запросов в одном соединении, сжатие заголовков и бинарный формат вместо нескольких параллельных соединений.

Ненадёжен ли HTTP/3, раз UDP не гарантирует доставку?

Нет: QUIC сам восстанавливает потерянные пакеты и шифрует данные. UDP служит только основой.

Что прозвучало в вашем ответе
  • HTTP/2 использует TCP, HTTP/3 использует QUIC поверх UDP.
  • QUIC обеспечивает надёжные потоки; UDP не означает потерю частей HTTP-ответа без восстановления.
  • Потеря в TCP может задержать все потоки соединения; QUIC разделяет доставку по потокам.

Спецификация: RFC 9114: HTTP/3 и QUIC

Сети и HTTP · Middle и выше

POST создания заказа завершился тайм-аутом. Можно ли автоматически повторить его? Как избежать двух заказов?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Какие методы HTTP идемпотентны?
  • Как повторять запросы, не перегружая сервер?

Разбор

Ответ мог потеряться после создания заказа. Слепой повтор POST способен создать второй. Согласуйте с API идентификатор операции или ключ идемпотентности, сохраняемый при повторах; сервер должен распознавать дубликаты. Сам заголовок без серверной реализации ничего не гарантирует. Идемпотентность означает одинаковый предполагаемый эффект повторов, а не обязательно одинаковый статус ответа.

Уточняющие вопросы

Какие методы HTTP идемпотентны?

GET, HEAD, PUT, DELETE, OPTIONS и TRACE идемпотентны по семантике HTTP. POST и PATCH не имеют такой гарантии по определению, но конкретная операция может быть реализована идемпотентно.

Как повторять запросы, не перегружая сервер?

С экспоненциальной задержкой и случайным разбросом (jitter) и с ограничением числа попыток.

Что прозвучало в вашем ответе
  • Тайм-аут не доказывает, что сервер не выполнил операцию.
  • POST не является идемпотентным по определению; повтор зависит от контракта API.
  • Нужен механизм дедупликации на сервере или проверка результата по идентификатору операции.

Напишите кодом

Спецификация: RFC 9110: идемпотентные методы

Сети и HTTP · Middle и выше

После успешного POST формы сервер отвечает перенаправлением. Чем 303 отличается от 307 и какой вариант подходит для перехода к странице результата?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что такое шаблон Post/Redirect/Get?
  • Чем 301 отличается от 308?

Разбор

Для перехода от отправки формы к просмотру результата подходит 303: браузер получает страницу результата отдельным GET. При 307 он сохраняет POST и тело, поэтому адрес назначения должен принимать эту операцию. Нельзя заменять эти статусы друг другом только потому, что оба перенаправляют. Такой переход к странице результата сам по себе не обеспечивает защиту от повторного создания заказа.

Уточняющие вопросы

Что такое шаблон Post/Redirect/Get?

После успешного POST сервер отвечает перенаправлением на страницу результата. Обновление страницы повторит безопасный GET, а не отправку формы.

Чем 301 отличается от 308?

Оба — постоянное перенаправление, но 308 гарантирует сохранение метода и тела, а при 301 браузеры исторически меняют POST на GET.

Что прозвучало в вашем ответе
  • 303 направляет к получению другого ресурса, обычно GET страницы результата.
  • 307 сохраняет метод и тело исходного запроса.
  • Выбор зависит от того, нужно показать результат или повторить операцию по другому адресу.

Спецификация: RFC 9110: перенаправления

Сети и HTTP · Middle и выше

API возвращает небольшой JSON, но экран долго загружается. Как по Network отличить медленную передачу от цепочки зависимых запросов?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как разорвать цепочку?
  • Что значит длинный этап Queueing?

Разбор

Посмотрите, начинается ли следующий запрос лишь после завершения предыдущего, и найдите инициатор. Долгое ожидание первого байта отличается от долгого скачивания: размер JSON не объясняет все задержки. Если код последовательно ждёт независимые данные, параллельный запуск может сократить цепочку. Сначала подтвердите отсутствие зависимости; одновременный запуск всех запросов не является универсальной оптимизацией.

Уточняющие вопросы

Как разорвать цепочку?

Запускать независимые запросы параллельно через Promise.all, заранее загружать важные ресурсы через preload или получать данные одним запросом.

Что значит длинный этап Queueing?

Запрос ждал свободного соединения или был отложен браузером из-за низкого приоритета.

Что прозвучало в вашем ответе
  • Сопоставляются Waterfall, Initiator и Timing отдельных запросов.
  • Различаются очередь, ожидание ответа и скачивание тела.
  • Независимые запросы можно запускать параллельно; реальные зависимости сохраняются.

Спецификация: Chrome DevTools: Network

Сети и HTTP · Junior

Какие HTTP-методы вы знаете и для чего каждый? Как бы вы назвали запросы для списка задач: получить все, создать, изменить, удалить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему удаление по ссылке GET /tasks/42/delete — плохая идея?
  • Чем PUT отличается от PATCH?

Разбор

Метод говорит серверу, что сделать с ресурсом по адресу:

  • GET — прочитать. Безопасный метод: клиент не запрашивает изменение состояния ресурса. Логирование на сервере допустимо; возможность кеширования зависит от ответа;
  • POST — создать или выполнить действие. Повтор может создать вторую запись;
  • PUT — заменить ресурс целиком; PATCH — изменить часть полей;
  • DELETE — удалить;
  • HEAD и OPTIONS — служебные: получить только заголовки и узнать, что разрешено (браузер шлёт OPTIONS перед некоторыми запросами CORS).

Для списка задач: GET /tasks — список, GET /tasks/42 — одна задача, POST /tasks — новая, PATCH /tasks/42 — отметить выполненной, DELETE /tasks/42 — удалить.

Важное слово — идемпотентность: GET, PUT и DELETE имеют одинаковый предполагаемый эффект при повторах, хотя статус ответа может различаться. Для POST такой гарантии по определению метода нет. Это важно, когда запрос нужно повторить после обрыва связи.

Уточняющие вопросы

Почему удаление по ссылке GET /tasks/42/delete — плохая идея?

Браузер, расширения и поисковые роботы считают GET безопасным и могут открыть ссылку заранее, например для предзагрузки. Задача удалится без действия пользователя.

Чем PUT отличается от PATCH?

PUT присылает ресурс целиком: незаполненные поля считаются удалёнными. PATCH присылает только то, что меняется.

Что прозвучало в вашем ответе
  • Названы GET, POST, PUT/PATCH и DELETE с назначением.
  • Предложены адреса и методы для списка задач.
  • GET не меняет данные на сервере.
  • Упомянута идемпотентность или то, что повтор POST может создать дубль.

Почитать на сайте

Спецификация: RFC 9110: методы HTTP

Сети и HTTP · Junior

Что означают группы кодов ответа 2xx, 3xx, 4xx и 5xx? Чем 401 отличается от 403, а 404 — от 500?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Какой код вернуть, если форма прислала неверный email?
  • Стоит ли автоматически повторять запрос при 404?

Разбор

Первая цифра кода сразу говорит, кто виноват и что делать:

  • 2xx — всё хорошо: 200 OK, 201 Created после создания, 204 No Content, когда тела нет;
  • 3xx — идите по другому адресу (301, 302, 303, 307, 308) или используйте кеш (304);
  • 4xx — проблема в запросе: 400 — неверные данные, 401 — нужна авторизация, 403 — доступ запрещён, 404 — не найдено, 429 — слишком много запросов;
  • 5xx — проблема на сервере: 500 — внутренняя ошибка, 502 — шлюз получил некорректный ответ вышестоящего сервера, 504 — не дождался ответа вовремя, 503 — сервис временно недоступен.

Для интерфейса это значит разное: при 401 предложить войти, при 403 объяснить, что нет прав, при 404 показать «не найдено», при 5xx — «попробуйте позже» и, возможно, повторить запрос.

Уточняющие вопросы

Какой код вернуть, если форма прислала неверный email?

Обычно 400 или 422 с описанием ошибки по полям, чтобы интерфейс показал сообщение рядом с нужным полем.

Стоит ли автоматически повторять запрос при 404?

Нет: ресурс не появится от повтора. Повторять имеет смысл временные ошибки — 503, 504, обрыв сети — и только безопасные или идемпотентные запросы.

Что прозвучало в вашем ответе
  • Объяснены все четыре группы: 2xx, 3xx, 4xx, 5xx.
  • 401 — нет авторизации, 403 — нет прав.
  • 404 — нет ресурса, 500 — ошибка сервера.
  • Сказано, как реагирует интерфейс на разные коды.

Почитать на сайте

Спецификация: RFC 9110: коды ответа

Сети и HTTP · Junior

Что происходит после того, как вы ввели адрес сайта в браузере и нажали Enter? Расскажите по шагам.

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему второй раз сайт открывается быстрее?
  • Где посмотреть все эти шаги?

Разбор

  1. Адрес. Браузер разбирает URL: протокол, домен, путь. Если это не адрес, а слова — отправляет их в поиск.
  2. DNS. Ищет IP-адрес домена: сначала в своём кеше, потом в системе, потом спрашивает DNS-сервер.
  3. Соединение. Устанавливает соединение с сервером, для HTTPS — ещё и защищённый канал TLS с проверкой сертификата.
  4. Запрос и ответ. Отправляет GET с заголовками и cookie, получает ответ: код, заголовки и HTML. Возможны перенаправления.
  5. Разбор. Строит из HTML дерево DOM, находит стили, скрипты и картинки и загружает их. Обычные скрипты останавливают разбор, пока не выполнятся.
  6. Отрисовка. Из DOM и CSS вычисляет стили, размеры и положение элементов (layout), рисует их (paint) и показывает на экране.

На собеседовании важно не перечислить всё, а показать, что вы понимаете цепочку и знаете, где она может тормозить.

Уточняющие вопросы

Почему второй раз сайт открывается быстрее?

Срабатывают кеши: DNS уже знает адрес, соединение может быть переиспользовано, а стили, скрипты и картинки берутся из HTTP-кеша браузера.

Где посмотреть все эти шаги?

Во вкладке Network в DevTools: у каждого запроса есть раздел Timing с DNS, соединением, TLS и ожиданием ответа.

Что прозвучало в вашем ответе
  • Назван поиск IP-адреса через DNS.
  • Названы соединение и TLS для HTTPS.
  • Названы HTTP-запрос и ответ с HTML.
  • Названы разбор HTML, загрузка ресурсов и отрисовка.

Почитать на сайте

Спецификация: HTML: разбор документа

Сети и HTTP · Junior

Из чего состоит HTTP-запрос и ответ? Как отправить на сервер новую задачу в формате JSON и как сервер поймёт, что пришёл именно JSON?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Можно ли передать данные в GET-запросе?
  • Что будет, если отправить объект без JSON.stringify?

Разбор

Код
POST /api/tasks HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json

{"title": "Купить молоко"}

Первая строка — метод и путь, затем заголовки, пустая строка и тело. Ответ устроен так же, но начинается с кода: HTTP/1.1 201 Created.

JavaScript
const response = await fetch('/api/tasks', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Купить молоко' }),
})
const created = await response.json()

Полезные заголовки: Content-Type — формат тела, Accept — какой формат ждём в ответе, Authorization — токен, Cookie и Set-Cookie — cookie, Cache-Control — правила кеширования.

Все части запроса видны во вкладке Network в DevTools: Headers, Payload и Response.

Уточняющие вопросы

Можно ли передать данные в GET-запросе?

Через параметры адреса: /api/tasks?done=true&page=2. Тело у GET не используют. Значения кодируют через URLSearchParams.

Что будет, если отправить объект без JSON.stringify?

Он превратится в строку "[object Object]", и сервер получит бессмыслицу.

Что прозвучало в вашем ответе
  • Названы метод, адрес, заголовки и тело запроса.
  • Названы код, заголовки и тело ответа.
  • JSON отправлен через JSON.stringify и Content-Type: application/json.

Почитать на сайте

Спецификация: RFC 9110: сообщения HTTP

Сети и HTTP · Junior

Что такое API и REST? Как бы вы описали адреса для статей блога и комментариев к ним?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как передать фильтры и сортировку?
  • Что такое документация API?

Разбор

Фронтенд не работает с базой данных напрямую — он просит сервер через API. В стиле REST:

  • адрес — существительное, ресурс: /articles, /articles/7, /articles/7/comments;
  • действие — метод HTTP: GET читать, POST создать, PATCH изменить, DELETE удалить;
  • результат — код статуса: 200, 201 Created, 404, 422 с ошибками проверки;
  • сервер не помнит предыдущие запросы: каждый запрос несёт всё нужное, например токен.

Для блога:

Код
GET    /articles?page=2        список статей, вторая страница
GET    /articles/7             одна статья
POST   /articles/7/comments    новый комментарий
DELETE /comments/15            удалить комментарий

Плохо: /getArticles, /deleteComment?id=15 — действия в адресе вместо методов.

Уточняющие вопросы

Как передать фильтры и сортировку?

Параметрами адреса: /articles?tag=css&sort=-date&page=2.

Что такое документация API?

Описание адресов, параметров, форм ответа и ошибок. Часто в формате OpenAPI, по которому можно сгенерировать типы для фронтенда.

Что прозвучало в вашем ответе
  • API объяснён как договорённость об обмене данными.
  • В REST адрес — ресурс, метод — действие.
  • Предложены адреса для статей и комментариев.
  • Названы коды статуса или формат JSON.

Спецификация: RFC 9110: семантика HTTP

Сети и HTTP · Middle и выше

Нужно показывать новые сообщения чата и курс валют в реальном времени. Чем отличаются опрос сервера (polling), long polling, Server-Sent Events и WebSocket, и что выбрать для каждой задачи?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему WebSocket может не работать в корпоративной сети?
  • Как не перегрузить сервер при массовом переподключении?

Разбор

  • Polling: setInterval с fetch. Работает везде, но данные приходят с задержкой до интервала, а пустые ответы нагружают сервер.
  • Long polling: сервер не отвечает, пока нет новостей, затем клиент сразу шлёт следующий запрос. Задержка меньше, но на каждое событие — новый запрос.
  • Server-Sent Events: new EventSource('/rates') — сервер пишет события в открытый HTTP-ответ. Только от сервера к клиенту, только текст, зато браузер сам переподключается и может отправить Last-Event-ID, если сервер задавал идентификаторы. Хранение и повторную выдачу пропущенных событий реализует сервер. Работает через обычную инфраструктуру HTTP.
  • WebSocket: new WebSocket('wss://…') — постоянный двусторонний канал с маленькими накладными расходами, текст и бинарные данные. Переподключение, проверку связи и восстановление пропущенного пишут сами.

Курс валют и уведомления — SSE: данные идут в одну сторону. Чат, совместное редактирование, игры — WebSocket. Если обновлений мало и задержка не важна, обычный polling раз в минуту честнее и проще.

При любом варианте нужно продумать обрыв связи: показать состояние, переподключиться с паузой и догрузить пропущенное.

Уточняющие вопросы

Почему WebSocket может не работать в корпоративной сети?

Некоторые прокси и файрволы обрывают долгие соединения или не пропускают обновление протокола. Поэтому библиотеки умеют откатываться на long polling.

Как не перегрузить сервер при массовом переподключении?

Увеличивать паузу перед повтором экспоненциально и добавлять случайный разброс, чтобы тысячи клиентов не вернулись в одну секунду.

Что прозвучало в вашем ответе
  • Объяснены polling и long polling.
  • SSE — односторонний поток с автопереподключением.
  • WebSocket — двусторонний канал.
  • Выбор обоснован для курса валют и чата, учтён обрыв связи.

Почитать на сайте

Спецификация: HTML: Server-Sent Events

Сети и HTTP · Middle и выше

Экран профиля делает шесть запросов к REST API и получает много лишних полей. Решит ли проблему GraphQL? Какие у него плюсы и минусы по сравнению с REST?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что такое проблема N+1?
  • Чем мутация отличается от запроса в GraphQL?

Разбор

Что даёт GraphQL. Клиент описывает нужную форму данных: пользователь, его последние заказы и адреса — в одном запросе POST /graphql. Нет лишних полей и цепочек запросов. Схема типов служит документацией и источником типов для TypeScript.

Чем платят:

  • кеш: в REST каждый ресурс — свой адрес, который кешируют браузер и CDN. В GraphQL запросы идут через один адрес, обычный HTTP-кеш для POST использовать сложнее. Возможны клиентский кеш, серверный кеш и GET-запросы для чтения, в том числе сохранённые запросы с устойчивым URL;
  • нагрузка: клиент может запросить глубокую вложенность. Нужны ограничения сложности и защита от проблемы N+1 на сервере;
  • ошибки: ответ часто 200 с полем errors, их нужно обрабатывать отдельно;
  • стоимость: новый сервер, инструменты и знания команды.

Варианты без смены технологии: адрес под экран (Backend for Frontend), параметр выбора полей ?fields=name,avatar, вложение связей ?include=orders, параллельные запросы через Promise.all вместо последовательных.

Уточняющие вопросы

Что такое проблема N+1?

Для списка из N пользователей сервер делает один запрос за списком и ещё N — за заказами каждого. Решают пакетной загрузкой, например через DataLoader.

Чем мутация отличается от запроса в GraphQL?

Запрос (query) только читает, мутация (mutation) меняет данные — аналог POST, PATCH и DELETE.

Что прозвучало в вашем ответе
  • GraphQL — одним запросом нужные поля, строгая схема.
  • Названы минусы: кеширование, нагрузка, ошибки или стоимость перехода.
  • Названы способы улучшить REST без смены технологии.

Спецификация: Спецификация GraphQL

Сети и HTTP · Junior

Как сайт «помнит», что вы вошли, если HTTP не хранит состояние между запросами? Чем вход через cookie сессии отличается от токена в localStorage?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему cookie сессии не уходит на запрос к API на другом домене?
  • Что такое cookie без Max-Age и Expires?

Разбор

HTTP не помнит прошлые запросы, поэтому каждый запрос должен нести «пропуск».

Cookie сессии. После проверки пароля сервер отвечает:

CSS
Set-Cookie: session=a8f3…; HttpOnly; Secure; SameSite=Lax; Max-Age=2592000

Дальше браузер сам прикладывает Cookie: session=a8f3… к запросам на этот сайт. Сервер находит сессию по идентификатору. HttpOnly прячет cookie от JavaScript, Secure отправляет только по HTTPS, SameSite ограничивает отправку с чужих сайтов, Max-Age задаёт срок.

Токен. Сервер отдаёт токен в теле ответа, код сохраняет его и добавляет Authorization: Bearer … к запросам. Удобно для мобильных приложений и API на другом домене, но если хранить токен в localStorage, при XSS его украдут.

Выход из аккаунта — удалить сессию на сервере, а не только cookie в браузере.

Уточняющие вопросы

Почему cookie сессии не уходит на запрос к API на другом домене?

Сначала проверьте, есть ли cookie именно для домена API и подходит ли её путь. credentials: "include" разрешает отправку подходящих cookie в cross-origin fetch, но не пересылает cookie чужого домена. Нужны согласованные CORS-заголовки; для cross-site сценария также учитываются SameSite=None; Secure и ограничения браузера на сторонние cookie.

Что такое cookie без Max-Age и Expires?

Сессионная cookie: удаляется при закрытии браузера (хотя некоторые браузеры восстанавливают сессии).

Что прозвучало в вашем ответе
  • Объяснён Set-Cookie и автоматическая отправка cookie.
  • Названы HttpOnly, Secure или SameSite.
  • Объяснён токен в заголовке Authorization.
  • Назван риск токена в localStorage при XSS.

Почитать на сайте

Спецификация: RFC 6265: HTTP-cookie

Сети и HTTP · Junior

Страница весит всего 300 КБ, но на мобильном интернете открывается 6 секунд. Чем задержка (latency) отличается от пропускной способности и почему число запросов иногда важнее их размера?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что такое TTFB?
  • Поможет ли сжатие gzip или Brotli с задержкой?

Разбор

Представьте трубу: ширина — пропускная способность, длина — задержка. Даже маленькому файлу нужно пройти всю длину туда и обратно.

Почему 300 КБ грузятся долго:

  • новое соединение: DNS, TCP и TLS — несколько кругов по 150 мс ещё до первого байта;
  • цепочки зависимостей: HTML → CSS → в CSS указан шрифт → в скрипте запрос к API. Каждый шаг ждёт предыдущий;
  • много доменов: на каждый — своё соединение.

Что помогает:

  • убрать цепочки: preload для важного, критический CSS в странице, запросы к API параллельно;
  • меньше доменов и preconnect к нужным;
  • HTTP/2 и HTTP/3 загружают много файлов по одному соединению;
  • кеш и CDN рядом с пользователем.

Проверять — с Throttling во вкладке Network и смотреть Waterfall: длинные «лестницы» и есть цепочки.

Уточняющие вопросы

Что такое TTFB?

Time To First Byte — время от отправки запроса до первого байта ответа. Включает сеть и время работы сервера.

Поможет ли сжатие gzip или Brotli с задержкой?

Сжатие уменьшает объём, то есть помогает с пропускной способностью. Число кругов ожидания оно не меняет.

Что прозвучало в вашем ответе
  • Объяснено отличие задержки от пропускной способности.
  • Названы цепочки зависимых запросов или затраты на соединение.
  • Названы способы: preload, параллельные запросы, HTTP/2, CDN или кеш.

Спецификация: Chrome DevTools: Waterfall и Timing

Сети и HTTP · Middle и выше

Пользователи из Владивостока жалуются, что сайт с сервером в Москве грузится медленно. Как помогут CDN и сжатие и какие файлы как кешировать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сбросить кеш CDN после выкладки?
  • Что такое stale-while-revalidate?

Разбор

CDN. 7000 км — это 100+ мс на каждый круг запроса, и несколько кругов на соединение. Узел CDN во Владивостоке устанавливает соединение рядом с пользователем, а файлы отдаёт из своего кеша. Сервер в Москве получает только запросы, которых нет в кеше.

Сжатие. Браузер присылает Accept-Encoding: br, gzip, сервер отвечает Content-Encoding: br. HTML, CSS, JS и SVG сжимаются в 3–5 раз. Картинки в JPEG, WebP, AVIF и шрифты WOFF2 уже сжаты — их повторно не жмут. Статические файлы сжимают заранее, при сборке, с максимальным уровнем.

Кеширование:

  • app.3f9a.js, style.81c2.css — новое содержимое получает новое имя, поэтому max-age=31536000, immutable;
  • HTML — no-cache (с проверкой по ETag) или короткий max-age, чтобы выкладка становилась видна быстро;
  • ответы с личными данными — private или no-store, чтобы CDN не отдал их другому человеку;
  • Vary: Accept-Encoding — чтобы кеш хранил сжатую и несжатую версии отдельно.

Уточняющие вопросы

Как сбросить кеш CDN после выкладки?

Для файлов с хешем не нужно — у них новое имя. HTML очищают командой purge у провайдера или держат с коротким сроком.

Что такое stale-while-revalidate?

Разрешение отдать устаревший ответ сразу и обновить кеш в фоне. Пользователь не ждёт, а данные почти свежие.

Что прозвучало в вашем ответе
  • CDN отдаёт файлы с ближайшего сервера и снижает задержку.
  • Названо сжатие Brotli или gzip для текстовых файлов.
  • Файлы с хешем — долгий кеш, HTML — короткий или с проверкой.
  • Персональные ответы — private или no-store.

Спецификация: RFC 9111: HTTP-кеширование

Сети и HTTP · Middle и выше

Какие заголовки безопасности стоит включить на сайте и от чего каждый защищает: Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options, Referrer-Policy?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему CSP часто ломает сайт при включении?
  • Чем опасен слишком большой max-age у HSTS?

Разбор

  • Strict-Transport-Security: max-age=31536000; includeSubDomains — после первого визита браузер сам переписывает http:// на https://. Политика запоминается только из защищённого ответа. Самый первый HTTP-визит до получения политики остаётся уязвимым; предварительное знание политики может дать список HSTS preload.
  • Content-Security-Policy: default-src 'self'; script-src 'self'; img-src 'self' https:; frame-ancestors 'none' — скрипты только со своего сайта, без встроенных onclick и <script> в разметке; встраивание сайта во фреймы запрещено. Такая политика блокирует многие способы исполнения внедрённого кода, но не гарантирует защиту от всех XSS, например при небезопасных скриптах на разрешённом источнике. Вводят постепенно через Content-Security-Policy-Report-Only, чтобы сначала собрать нарушения.
  • X-Content-Type-Options: nosniff — браузер не выполнит как скрипт файл, который сервер отдал как картинку или текст.
  • Referrer-Policy: strict-origin-when-cross-origin — другим сайтам уходит только домен, без пути и параметров, где могут быть личные данные.
  • Permissions-Policy — отключить ненужные камеру, микрофон, геолокацию.

Заголовки не заменяют правильный код: экранирование вывода, проверка данных на сервере и защита от CSRF нужны всё равно.

Уточняющие вопросы

Почему CSP часто ломает сайт при включении?

Встроенные скрипты и обработчики в атрибутах, сторонние счётчики и шрифты оказываются запрещены. Нужно перенести код в файлы или разрешить конкретные скрипты по nonce или хешу.

Чем опасен слишком большой max-age у HSTS?

Если HTTPS сломается, браузеры не пустят пользователей на сайт по HTTP до истечения срока. Поэтому начинают с короткого срока.

Что прозвучало в вашем ответе
  • HSTS — только HTTPS.
  • CSP — источники ресурсов и защита от XSS, вводится через Report-Only.
  • Названы nosniff или Referrer-Policy.
  • Заголовки дополняют, а не заменяют защиту в коде.

Спецификация: CSP Level 3

Сети и HTTP · Junior

Удаление записи прошло успешно: сервер вернул 204. Почему await response.json() падает и как обработать такой ответ?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем null в JSON отличается от пустого тела?
  • Есть ли тело у ответа на HEAD?

Разбор

JavaScript
const response = await fetch("/api/items/42", { method: "DELETE" })
if (!response.ok) throw new Error("HTTP " + response.status)
if (response.status === 204) {
  removeItemFromView(42)
} else {
  const result = await response.json()
  renderResult(result)
}

В примере остальные успешные ответы по контракту содержат JSON. Успешный статус сам по себе не обещает JSON: формат и наличие тела должны быть оговорены. Пустая строка не является корректным JSON; возвращать вместо ошибки пустой объект опасно.

Уточняющие вопросы

Чем null в JSON отличается от пустого тела?

null — корректный JSON из четырёх символов, а у пустого тела нет JSON-значения для разбора.

Есть ли тело у ответа на HEAD?

Нет: метод получает метаданные представления без содержимого ответа.

Что прозвучало в вашем ответе
  • 204 объяснён как успех без тела.
  • Вызов json() исключён для пустого ответа.
  • Формат остальных ответов определяется контрактом.

Спецификация: Первоисточник: network

Сети и HTTP · Middle и выше

Два редактора открыли одну статью. Второй сохранил старую версию поверх правок первого. Как сервер и клиент могут обнаружить такой конфликт?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему сравнение версии должно быть атомарным?
  • Чем If-None-Match при чтении отличается от If-Match при записи?

Разбор

Получив ETag: "v7", клиент отправляет PUT с If-Match: "v7". Сервер атомарно сравнивает текущую версию и применяет изменение только при совпадении. Если другой редактор уже создал версию 8, ответ 412 сообщает о невыполненном условии.

Интерфейс сохраняет локальный черновик, предлагает загрузить актуальную версию и сопоставить изменения. Молчаливый повтор без условия снова создаёт риск потери правок. Защита от дублей запроса и защита от устаревшего редактирования решают разные задачи.

Уточняющие вопросы

Почему сравнение версии должно быть атомарным?

Между отдельной проверкой и записью может успеть сохраниться другой редактор.

Чем If-None-Match при чтении отличается от If-Match при записи?

Первое обычно позволяет не пересылать неизменившееся представление; второе разрешает изменение только ожидаемой версии.

Что прозвучало в вашем ответе
  • Клиент отправляет версию через If-Match.
  • Сервер атомарно проверяет условие и сообщает конфликт.
  • Локальный черновик сохраняется для разрешения конфликта.

Спецификация: Первоисточник: network

Сети и HTTP · Middle и выше

Поиск получил 429 и Retry-After: 30. Нужно ли сразу повторить запрос и как избежать лавины повторов от клиентов?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делать, если заголовок отсутствует или неверен?
  • Почему одинаковая задержка у всех клиентов неудобна?

Разбор

Retry-After бывает числом секунд или HTTP-датой, поэтому его нельзя всегда читать как миллисекунды. При известной минимальной задержке случайный разброс добавляют поверх неё, а не сокращают ожидание.

Для поиска пользователь может уже изменить запрос: повторять старый не нужно. Покажите состояние ожидания, соблюдайте предел попыток и учитывайте отмену. Повтор изменяющей операции допустим только при согласованной идемпотентности; статус 429 не делает любой запрос безопасным для повторения.

Уточняющие вопросы

Что делать, если заголовок отсутствует или неверен?

Использовать ограниченную политику задержек приложения, а не немедленный бесконечный цикл.

Почему одинаковая задержка у всех клиентов неудобна?

Они одновременно повторят запросы. Случайный разброс распределяет нагрузку во времени.

Что прозвучало в вашем ответе
  • 429 распознан как ограничение частоты.
  • Учтены оба формата Retry-After.
  • Повторы ограничены и учитывают актуальность операции.

Напишите кодом

Спецификация: Первоисточник: network

Сети и HTTP · Middle и выше

Сервер отдаёт по одному URL русский и английский текст в зависимости от Accept-Language. Почему общий кеш может перепутать версии и зачем нужен Vary?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Заменяет ли Vary заголовок Cache-Control?
  • Когда нужен Vary: Origin?

Разбор

Если представление зависит от заголовка запроса, одного URL недостаточно для выбора ответа. Vary описывает дополнительные измерения ключа кеша. Срок и возможность хранения задаются отдельно, например через Cache-Control.

Для локализованных страниц отдельные URL часто проще: ссылкой можно поделиться, а число вариантов кеша предсказуемо. Не добавляйте Vary: Cookie механически ко всем страницам — это создаёт множество вариантов. Персональные ответы требуют осознанного private или no-store.

Уточняющие вопросы

Заменяет ли Vary заголовок Cache-Control?

Нет, он определяет выбор варианта, а не его срок годности или разрешение на хранение.

Когда нужен Vary: Origin?

Когда сервер формирует разные CORS-ответы по заголовку Origin, например отражает разрешённый источник.

Что прозвучало в вашем ответе
  • Объяснено влияние заголовка запроса на вариант ответа.
  • Vary отличён от политики хранения.
  • Персональные данные не объявлены безопасными только из-за Vary.

Спецификация: Первоисточник: network

Сети и HTTP · Middle и выше

Бэкенд предлагает авторизацию через JWT: access-токен на 15 минут и refresh-токен на 30 дней. Чем это отличается от серверной сессии, где хранить токены в браузере и как выйти из всех устройств?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему нельзя принимать JWT с алгоритмом none или выбирать алгоритм из заголовка токена?
  • Как фронтенду обновлять access-токен при нескольких параллельных запросах?

Разбор

Сессия: сервер хранит запись, клиент — cookie с идентификатором. Выход — удаление записи. Нужно общее хранилище сессий для нескольких серверов.

JWT: заголовок, данные (claims) и подпись в base64url. Данные не зашифрованы — их может прочитать любой, поэтому секреты туда не кладут. Удобно для нескольких сервисов, но отозвать выданный токен нельзя без чёрного списка.

Типичная схема:

  • access-токен живёт минуты, хранится в памяти приложения и отправляется в Authorization: Bearer;
  • refresh-токен — в cookie HttpOnly; Secure; SameSite=Strict на путь обновления, недоступен скриптам;
  • при обновлении сервер выдаёт новую пару и помечает старый refresh использованным (ротация); повторное использование старого — признак кражи, все токены семьи отзываются.

Хранение токена в localStorage делает его доступным при XSS. Cookie защищены от чтения скриптом, но требуют защиты от CSRF.

Уточняющие вопросы

Почему нельзя принимать JWT с алгоритмом none или выбирать алгоритм из заголовка токена?

Злоумышленник сможет подделать токен без подписи или подменить алгоритм. Сервер должен проверять подпись строго ожидаемым алгоритмом и ключом.

Как фронтенду обновлять access-токен при нескольких параллельных запросах?

Одним общим запросом обновления: остальные запросы ждут его результата, а не запускают своё обновление каждый.

Что прозвучало в вашем ответе
  • Различены сессия и самодостаточный токен.
  • Названа проблема отзыва JWT.
  • Описаны короткий access и refresh в HttpOnly cookie.
  • Упомянута ротация refresh-токенов или выход везде.

Спецификация: RFC 7519: JSON Web Token

Сети и HTTP · Middle и выше

Нужно добавить кнопку «Войти через VK ID / Google». Как устроен вход через OAuth 2.0 с кодом авторизации и зачем одностраничному приложению PKCE?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем scope важен для пользователя?
  • Что будет, если не проверять state?

Разбор

Поток Authorization Code с PKCE:

  1. приложение создаёт code_verifier и state, перенаправляет на провайдера с client_id, redirect_uri, scope, state и code_challenge — SHA-256 от verifier;
  2. пользователь входит у провайдера и соглашается дать доступ;
  3. провайдер возвращает на redirect_uri с code и state; приложение сверяет state;
  4. код обменивается на токены вместе с code_verifier.

OAuth сам по себе про доступ к API, а не про личность. Для входа нужен OpenID Connect: он добавляет id_token с идентификатором пользователя, который проверяют.

Надёжнее всего, когда обмен кода и хранение токенов делает собственный бэкенд (паттерн BFF), а браузер получает обычную сессионную cookie. Устаревший implicit flow с токеном в адресе не используют. redirect_uri должен точно совпадать с зарегистрированным.

Уточняющие вопросы

Чем scope важен для пользователя?

Он определяет права приложения. Запрашивают минимум нужного — лишние права пугают и увеличивают ущерб при утечке.

Что будет, если не проверять state?

Злоумышленник может подсунуть свой код авторизации, и жертва войдёт в чужой аккаунт или привяжет его к своему (login CSRF).

Что прозвучало в вашем ответе
  • Описаны редирект, код и обмен на токены.
  • Объяснено назначение PKCE.
  • Назван state.
  • Различены OAuth и OpenID Connect.

Спецификация: RFC 7636: PKCE

Доступность · Junior

Дизайнер предлагает кликабельный div вместо кнопки. Что нужно для доступного действия «Удалить» и когда вместо button нужна ссылка?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что придётся сделать, если всё-таки использовать div?
  • Зачем type="button" внутри формы?

Разбор

Для удаления используйте нативный button, а для навигации ссылку с href. Кнопка уже поддерживает фокус и активацию Enter/Пробелом. Иконке удаления нужно доступное имя, например aria-label="Удалить запись". Одна роль button на div не добавляет клавиатурного поведения. Видимый фокус помогает понять, где находится пользователь. Внутри формы задайте type="button", если отправка не нужна.

Уточняющие вопросы

Что придётся сделать, если всё-таки использовать div?

Добавить role="button", tabindex="0", обработку Enter и пробела, состояние disabled и видимый фокус. Проще и надёжнее взять button и сбросить его стили.

Зачем type="button" внутри формы?

По умолчанию кнопка внутри формы имеет type="submit" и отправит форму. Для кнопки «Удалить строку» это неожиданное поведение.

Что прозвучало в вашем ответе
  • Для действия выбран button, для перехода a с href.
  • Кнопка доступна с клавиатуры и имеет понятное имя, в том числе если видна только иконка.
  • Фокус заметен; у кнопки внутри формы при необходимости указан type="button".

Почитать на сайте

Спецификация: WAI: поведение кнопок

Доступность · Middle и выше

Модальное окно видно, но Tab уходит на страницу за ним. Какие требования нарушены и что нужно сделать при открытии и закрытии?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сделать фон недоступным?
  • Куда ставить фокус при открытии?

Разбор

Модальность требует не только затемнения фона. Нужны доступное имя, начальный фокус, ограничение Tab внутри и недоступный фон. Escape и видимая кнопка дают способ закрытия. После закрытия возвращайте фокус к инициатору, если он существует; иначе выбирайте логичное продолжение. Нативный dialog с showModal() даёт часть поведения, но выбор начального фокуса и проверка сценария остаются за разработчиком.

Уточняющие вопросы

Как сделать фон недоступным?

showModal() делает это сам. Для своей реализации — атрибут inert на остальном содержимом страницы.

Куда ставить фокус при открытии?

На первое поле формы или на заголовок окна, если сначала нужно прочитать текст. Ставить его сразу на кнопку удаления в окне подтверждения опасно.

Что прозвучало в вашем ответе
  • Фокус переводится в диалог и остаётся внутри при Tab/Shift+Tab.
  • Фон недоступен для взаимодействия, есть имя и способ закрытия.
  • Escape закрывает; после закрытия фокус обычно возвращается к инициатору.

Почитать на сайте

Спецификация: WAI: модальный диалог

Доступность · Junior

Одна и та же иконка используется как украшение рядом с текстом и как единственное содержимое ссылки на корзину. Как отличается альтернативный текст?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем alt="" отличается от отсутствия alt?
  • Как подписать inline-SVG иконку?

Разбор

В первом случае используйте alt="", чтобы не дублировать соседний текст. Во втором нужен смысл действия, например «Корзина», а не «серая тележка». Если ссылка уже имеет другое доступное имя, изображение может оставаться декоративным. Для графика одного названия недостаточно: значимые данные требуют текстового эквивалента. Решение зависит от функции изображения на конкретной странице.

Уточняющие вопросы

Чем alt="" отличается от отсутствия alt?

Пустой alt говорит «это украшение, пропусти». Без alt скринридер может прочитать имя файла, например «icon-cart-24.svg».

Как подписать inline-SVG иконку?

Для декоративной — aria-hidden="true". Для значимой — role="img" и aria-label или элемент <title> внутри SVG.

Что прозвучало в вашем ответе
  • У декоративной картинки рядом с достаточным текстом alt пустой.
  • У единственного изображения в ссылке текст передаёт назначение ссылки.
  • alt описывает смысл в контексте, а не имя файла или все визуальные детали.

Почитать на сайте

Спецификация: WAI: выбор альтернативного текста

Доступность · Middle и выше

После отправки неверное поле лишь обводится красным. Почему этого недостаточно? Как связать текст ошибки с полем и помочь исправить форму?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Когда показывать ошибку: при вводе или при отправке?
  • Как сообщить об ошибке скринридеру без перевода фокуса?

Разбор

Покажите причину и способ исправления рядом с полем, сохраните введённые данные. Свяжите сообщение с input, выставьте aria-invalid при ошибке. После отправки можно перевести фокус к первой ошибке или к сводке со ссылками на поля. Динамическое уведомление должно быть доступно скринридеру, но не нужно объявлять все поля при каждом символе. Placeholder не заменяет постоянную подпись.

Уточняющие вопросы

Когда показывать ошибку: при вводе или при отправке?

Обычно при уходе из поля или при отправке. Ошибка на каждый символ мешает: человек ещё не закончил ввод, а скринридер постоянно объявляет её.

Как сообщить об ошибке скринридеру без перевода фокуса?

Через область с role="alert" или aria-live: её содержимое объявляется при изменении.

Что прозвучало в вашем ответе
  • Ошибка описана словами и не зависит только от цвета.
  • Поле имеет label; сообщение связано через aria-describedby или подходящий механизм.
  • Используются aria-invalid и понятная стратегия фокуса/уведомления без потери ввода.

Почитать на сайте

Спецификация: WAI: сообщения об ошибках формы

Доступность · Junior

Что такое доступность сайта и кому она нужна? Что можно проверить на своей странице за пять минут без специальных программ?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что такое WCAG?
  • Хватит ли проверки Lighthouse, чтобы считать сайт доступным?

Разбор

Доступность (a11y) нужна не только людям с инвалидностью. Подписи к полям помогают всем, контраст — тем, кто читает на ярком солнце, а работа с клавиатуры — тем, у кого сломалась мышь. Поисковики тоже лучше понимают страницу с хорошей разметкой.

Быструю проверку можно сделать руками:

  • пройти страницу клавишей Tab: видно ли, где фокус, и можно ли нажать каждую кнопку и ссылку;
  • проверить, что у содержательных картинок есть alt, а у декоративных — пустой alt="";
  • убедиться, что у каждого поля формы есть <label>;
  • увеличить масштаб до 200% — не разваливается ли страница;
  • проверить контраст текста, например во вкладке Elements в DevTools.

Есть и автоматические проверки, например Lighthouse, но они находят только часть проблем: понятность текста ссылки или порядок фокуса нужно смотреть самому.

Уточняющие вопросы

Что такое WCAG?

Рекомендации по доступности веб-содержимого от W3C. В них есть уровни A, AA и AAA; на практике обычно ориентируются на AA — например, контраст обычного текста не меньше 4.5:1.

Хватит ли проверки Lighthouse, чтобы считать сайт доступным?

Нет. Автоматика ловит отсутствие alt или низкий контраст, но не поймёт, что alt бессмысленный или что фокус прыгает в неожиданное место. Нужна ручная проверка с клавиатуры и, по возможности, со скринридером.

Что прозвучало в вашем ответе
  • Доступность объяснена как удобство для людей с разными ограничениями, а не только для незрячих.
  • Названа проверка с клавиатуры: Tab, видимый фокус, все элементы нажимаются.
  • Названы альтернативный текст картинок и подписи полей.
  • Названы контраст или масштабирование.

Почитать на сайте

Спецификация: WCAG 2.2: рекомендации по доступности

Доступность · Junior

Как проверить, что страницей можно пользоваться без мыши? Что делает атрибут tabindex и почему значения больше нуля обычно вредят?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему нельзя просто убрать обводку фокуса, если она некрасивая?
  • Зачем ссылка «Перейти к содержимому» в начале страницы?

Разбор

Ссылки, кнопки и поля формы получают фокус сами — поэтому для действий лучше брать именно их. Проверка с клавиатуры: Tab переходит вперёд, Shift+Tab — назад, Enter нажимает ссылку и кнопку, пробел — кнопку и флажок, Escape закрывает окна.

tabindex меняет поведение фокуса:

  • tabindex="0" — элемент попадает в обход в порядке кода; нужен редко, например для своей виджет-обёртки;
  • tabindex="-1" — элемент не попадает в обход, но на него можно перевести фокус через focus(): так переводят фокус на заголовок окна или на сообщение об ошибке;
  • положительное значение ставит элемент в начало обхода раньше всех остальных. На реальной странице это быстро превращается в путаницу.

Порядок фокуса должен совпадать с порядком чтения. Если элементы переставлены только через CSS, например order во Flexbox, фокус пойдёт по коду, а не по экрану.

Уточняющие вопросы

Почему нельзя просто убрать обводку фокуса, если она некрасивая?

Без неё человек с клавиатуры не видит, где находится. Обводку можно перерисовать в стиле сайта, например через :focus-visible, но не удалять без замены.

Зачем ссылка «Перейти к содержимому» в начале страницы?

Чтобы не проходить Tab-ом всё меню на каждой странице. Ссылка ведёт на <main> и обычно появляется только при фокусе.

Что прозвучало в вашем ответе
  • Названа проверка Tab и Shift+Tab: фокус виден и доходит до всех элементов.
  • Объяснены tabindex="0" и tabindex="-1".
  • Положительный tabindex ломает естественный порядок обхода.
  • Порядок фокуса совпадает с порядком чтения.

Почитать на сайте

Спецификация: HTML: порядок фокуса и tabindex

Доступность · Junior

Что такое ARIA и когда её стоит использовать? Почему говорят, что лучшая ARIA — та, без которой удалось обойтись?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем aria-label отличается от видимой подписи?
  • Что будет, если повесить aria-hidden="true" на кнопку?

Разбор

Скринридер узнаёт о странице через дерево доступности: у каждого элемента есть роль (кнопка, ссылка, заголовок), доступное имя и состояние (нажата, развёрнута). Нативные элементы заполняют это дерево сами. ARIA позволяет дописать то, чего HTML не умеет, например aria-expanded="true" у кнопки, которая раскрывает меню.

Первое правило ARIA: если есть HTML-элемент с нужным смыслом и поведением, используйте его. <button> уже фокусируется, нажимается Enter и пробелом и называется кнопкой. <div role="button"> только называется кнопкой — клавиатуру, фокус и состояние придётся писать самому.

Частые полезные атрибуты: aria-label — имя для кнопки-иконки, aria-describedby — связь с подсказкой или ошибкой, aria-expanded — состояние раскрытия, aria-live — объявлять изменения, aria-hidden="true" — спрятать декоративное от скринридера.

Уточняющие вопросы

Чем aria-label отличается от видимой подписи?

aria-label слышит только скринридер. Если подпись видна на экране, лучше связать её с элементом, например через <label> или aria-labelledby: тогда зрячий и незрячий пользователи видят и слышат одно и то же.

Что будет, если повесить aria-hidden="true" на кнопку?

Скринридер её не увидит, но с клавиатуры на неё по-прежнему можно попасть: пользователь окажется на «пустом» элементе. aria-hidden ставят только на то, что не нужно для работы.

Что прозвучало в вашем ответе
  • ARIA сообщает роль, имя и состояние, но не меняет поведение.
  • Сначала выбирают нативный HTML-элемент, ARIA — когда его нет.
  • Назван хотя бы один уместный пример: aria-label, aria-expanded, aria-describedby или aria-live.

Почитать на сайте

Спецификация: Using ARIA: правила применения

Доступность · Middle и выше

После нажатия «В корзину» появляется уведомление «Товар добавлен» и через три секунды исчезает. Как сделать, чтобы его услышал пользователь скринридера, и чем role="status" отличается от role="alert"?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему не перевести фокус на уведомление?
  • Зачем aria-atomic="true"?

Разбор

Живая область — элемент, изменения которого скринридер объявляет сам:

  • role="status" = aria-live="polite": дождётся паузы. Подходит для «Товар добавлен», «Сохранено», «Найдено 12 результатов»;
  • role="alert" = aria-live="assertive": прервёт текущее чтение. Только для срочного: «Сессия истекает», ошибка оплаты.
HTML
<div role="status" class="toast"></div>
…
toast.textContent = 'Товар добавлен в корзину'

Главная ошибка — вставлять в страницу новый элемент с role="status" вместе с текстом: многие скринридеры следят только за областями, которые уже были. Поэтому пустой контейнер кладут в разметку заранее.

Исчезающее через три секунды уведомление не успеет прочитать и человек, который медленно читает. Сообщение должно висеть дольше или до закрытия, а важное действие — например «Отменить» — не стоит прятать во всплывающем окне.

Уточняющие вопросы

Почему не перевести фокус на уведомление?

Человек потеряет место, где был, и придётся возвращаться к товару. Фокус переводят, только когда дальше нужно действие в этом месте, например в окне подтверждения.

Зачем aria-atomic="true"?

Чтобы при изменении части текста, например счётчика, объявлялась вся область целиком: «В корзине 3 товара», а не просто «3».

Что прозвучало в вашем ответе
  • Названа живая область: aria-live или роль status/alert.
  • Объяснено отличие вежливого и срочного объявления.
  • Контейнер существует заранее, меняется текст.
  • Учтено время показа или закрытие вручную.

Спецификация: WAI-ARIA: живые области

Доступность · Middle и выше

Нужны вкладки «Описание», «Характеристики», «Отзывы». Какие роли и атрибуты понадобятся и как должна работать клавиатура?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему не сделать все вкладки доступными по Tab?
  • Зачем панели tabindex="0"?

Разбор

HTML
<div role="tablist" aria-label="О товаре">
  <button role="tab" id="t1" aria-selected="true" aria-controls="p1">Описание</button>
  <button role="tab" id="t2" aria-selected="false" aria-controls="p2" tabindex="-1">Характеристики</button>
</div>
<div role="tabpanel" id="p1" aria-labelledby="t1">…</div>
<div role="tabpanel" id="p2" aria-labelledby="t2" hidden>…</div>

Клавиатура по шаблону WAI-ARIA:

  • Tab приводит на активную вкладку, следующий Tab — в панель, а не на соседние вкладки. Для этого у неактивных tabindex="-1" (приём «блуждающего tabindex»);
  • стрелки влево и вправо переводят фокус между вкладками, Home и End — на первую и последнюю;
  • переключение сразу при переходе стрелкой или по Enter — выбирают в зависимости от того, быстро ли загружается панель.

При смене вкладки обновляют aria-selected, tabindex и hidden у панелей. Если «вкладки» на деле ведут на разные страницы, это навигация: ссылки с aria-current="page", а не роли вкладок.

Уточняющие вопросы

Почему не сделать все вкладки доступными по Tab?

Это допустимо, но шаблон ожидает одну точку входа: пользователь скринридера знает, что внутри списка вкладок работают стрелки, и не проходит Tab-ом каждый пункт.

Зачем панели tabindex="0"?

Если в панели нет фокусируемых элементов, без него следующий Tab перескочит через неё, и текст панели сложно найти.

Что прозвучало в вашем ответе
  • Названы роли tablist, tab, tabpanel.
  • Названы aria-selected и связь вкладки с панелью.
  • Стрелки переключают вкладки, Tab ведёт в панель.
  • Назван блуждающий tabindex или tabindex="-1" у неактивных.

Почитать на сайте

Спецификация: ARIA APG: шаблон вкладок

Доступность · Middle и выше

Как проверить страницу скринридером, если вы им никогда не пользовались? Что именно смотреть и какие сочетания браузера и программы брать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему скринридер читает кнопку как «кнопка» без названия?
  • Что такое режим просмотра и режим фокуса в NVDA?

Разбор

Скринридеры ведут себя по-разному, поэтому проверяют на тех сочетаниях, которыми реально пользуются: NVDA и JAWS на Windows, VoiceOver на Apple, TalkBack на Android.

Что проверять:

  • Навигация по заголовкам (в NVDA — клавиша H): читается ли структура страницы как оглавление;
  • Области (D в NVDA): есть ли шапка, навигация, основное содержимое, подвал;
  • Список ссылок и полей: понятны ли тексты без контекста — «Подробнее» десять раз подряд не помогает;
  • Элементы управления: у каждого есть имя, роль и состояние — «Меню, кнопка, свёрнуто»;
  • Динамика: объявляются ли ошибки, результаты поиска, уведомления;
  • Главный сценарий целиком: найти товар, положить в корзину, оформить заказ.

Перед этим полезно посмотреть дерево доступности в DevTools (панель Accessibility): там видно имя и роль каждого элемента. Но настоящий опыт даёт только прохождение сценария на слух, а лучше всего — тестирование с людьми, которые пользуются скринридером каждый день.

Уточняющие вопросы

Почему скринридер читает кнопку как «кнопка» без названия?

У неё нет доступного имени: внутри только иконка без текста. Нужен видимый текст, скрытый текст для скринридера или aria-label.

Что такое режим просмотра и режим фокуса в NVDA?

В режиме просмотра клавиши-буквы перемещают по элементам страницы, в режиме фокуса — вводят текст в поле. Сложные виджеты иногда ломают переключение между режимами.

Что прозвучало в вашем ответе
  • Названы реальные сочетания скринридера и браузера.
  • Названа навигация по заголовкам и областям.
  • Проверены имена ссылок, кнопок и полей.
  • Пройден главный сценарий целиком.

Почитать на сайте

Спецификация: W3C WAI: оценка доступности

Доступность · Junior

Дизайнер предлагает светло-серый текст на белом фоне и выделять обязательные поля только красной рамкой. Что не так и как проверить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Нужно ли проверять контраст текста на картинке?
  • Что такое тёмная тема с точки зрения контраста?

Разбор

Контраст. Рекомендации WCAG уровня AA: обычный текст — 4.5:1, крупный (от 24px или 18.7px жирным) и значимые элементы интерфейса вроде рамки поля — 3:1. Серый #999 на белом — около 2.8:1, это мало. При выборе элемента в DevTools во вкладке Elements в подсказке показан коэффициент контраста.

Не только цвет. Около 8% мужчин плохо различают красный и зелёный, а на ярком солнце цвета видны хуже всем. Поэтому:

  • обязательное поле — словом «обязательно» или звёздочкой с пояснением в начале формы, плюс атрибут required;
  • ошибка — текстом рядом с полем, а не только красной рамкой;
  • ссылки в тексте — подчёркиванием, а не только цветом;
  • на графиках — подписи или разные узоры линий.

Проверить «не только цветом» просто: посмотреть страницу в оттенках серого — в DevTools есть эмуляция нарушений цветового зрения.

Уточняющие вопросы

Нужно ли проверять контраст текста на картинке?

Да. Помогает однотонная подложка. Проверяют место с наименьшим контрастом: для светлого текста обычно самый светлый фон, для тёмного — самый тёмный.

Что такое тёмная тема с точки зрения контраста?

Те же требования, но наоборот. Чисто белый текст на чёрном фоне некоторым режет глаза, поэтому берут чуть смягчённые цвета с достаточным контрастом.

Что прозвучало в вашем ответе
  • Названы требования к контрасту: 4.5:1 для обычного текста.
  • Информацию нельзя передавать только цветом.
  • Предложено подписать обязательные поля и ошибки текстом.
  • Названа проверка в DevTools или калькулятором.

Почитать на сайте

Спецификация: WCAG 2.2: контраст

Доступность · Junior

В карточках новостей стоит ссылка «Подробнее», а в тексте — «нажмите здесь». Почему это плохо для доступности и как переписать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Можно ли сделать ссылкой всю карточку?
  • Нужно ли писать «ссылка на…» в тексте?

Разбор

Ссылку читают вне контекста: в списке ссылок, при переходе Tab-ом, поисковик — как описание целевой страницы. «Подробнее», «здесь», «тут» ничего не сообщают.

Как исправить:

  • написать цель прямо: «Скачать прайс-лист (PDF, 2 МБ)», «Тарифы для бизнеса»;
  • в карточке сделать ссылкой заголовок — он уже описывает новость, — а «Подробнее» убрать или оставить декоративно;
  • если короткая видимая подпись обязательна — дописать скрытую часть: Подробнее<span class="visually-hidden"> о запуске тарифа</span>;
  • предупреждать о необычном: открытие в новой вкладке, скачивание файла.

aria-label тоже заменяет имя ссылки, но тогда видимый текст и то, что слышит скринридер, расходятся. Люди, управляющие голосом, говорят «нажать Подробнее» — и программа может не найти ссылку.

Уточняющие вопросы

Можно ли сделать ссылкой всю карточку?

Можно, но тогда скринридер прочитает весь текст карточки как имя ссылки. Чаще ссылкой делают заголовок, а кликабельность всей карточки добавляют через растянутый псевдоэлемент.

Нужно ли писать «ссылка на…» в тексте?

Нет, скринридер сам сообщает роль «ссылка».

Что прозвучало в вашем ответе
  • Объяснено, что ссылки читают вне контекста, например списком.
  • Текст ссылки описывает цель.
  • Предложен способ для карточки: ссылка-заголовок или скрытый текст.

Почитать на сайте

Спецификация: WCAG 2.2: назначение ссылки

Доступность · Junior

Слабовидящий пользователь увеличивает страницу до 200–400%. Что на ней может сломаться и как вёрстка должна это выдержать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как быстро проверить?
  • А таблицы и карты?

Разбор

Увеличение в браузере на 400% при ширине окна 1280px равно экрану шириной 320px. Если страница адаптивна, она перестраивается в одну колонку — это требование WCAG «Reflow».

Что ломается:

  • фиксированная высота блока: текст вылезает или обрезается overflow: hidden;
  • фиксированная ширина в px: появляется горизонтальная прокрутка;
  • шрифты в px не увеличиваются, если человек изменил только размер текста в настройках браузера;
  • закреплённые шапка и плашки занимают пол-экрана;
  • текст в картинках становится размытым.

Как сделать правильно: rem для шрифтов, min-height вместо height, max-width и % вместо фиксированной ширины, Flexbox и Grid с переносами, медиазапросы. В метатеге viewport не указывать maximum-scale=1 и user-scalable=no.

Уточняющие вопросы

Как быстро проверить?

Нажать Ctrl + «плюс» до 400% на окне шириной около 1280px и пройти страницу: всё ли читается без горизонтальной прокрутки и ничего ли не перекрыто.

А таблицы и карты?

Им горизонтальная прокрутка допустима внутри своего блока: двумерные данные нельзя перестроить в одну колонку без потери смысла.

Что прозвучало в вашем ответе
  • Названо, что ломается: обрезка, наложение, горизонтальная прокрутка.
  • Названы rem и гибкие размеры вместо фиксированных.
  • Нельзя запрещать масштаб через viewport.

Почитать на сайте

Спецификация: WCAG 2.2: Reflow

Доступность · Junior

На сайте курса есть видеоуроки и фоновое видео на главной. Что нужно сделать, чтобы ими могли пользоваться глухие и слабослышащие, незрячие и люди, которым мешает движение?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем субтитры (captions) отличаются от subtitles?
  • Почему браузеры блокируют автовоспроизведение со звуком?

Разбор

  • Субтитры — текст речи и важных звуков синхронно с видео. Подключаются файлом WebVTT: <track kind="captions" src="lesson.vtt" srclang="ru" label="Русский">. Автоматические субтитры нужно вычитать: ошибки в терминах сбивают с толку.
  • Расшифровка — весь текст урока под видео. Её можно прочитать, найти поиском, перевести, и она помогает тем, кто смотрит без звука в транспорте.
  • Описание изображения — если в видео показывают код или схему и не проговаривают, незрячий пользователь это пропустит. Решение — проговаривать в самом уроке или дать текстовое описание.
  • Управление — атрибут controls или свои кнопки, доступные с клавиатуры и с подписями.
  • Фоновое видео — muted, кнопка паузы, остановка при prefers-reduced-motion: reduce. WCAG требует способ приостановить, остановить или скрыть автоматически начавшееся движение длительностью больше 5 секунд, показанное параллельно другому содержимому, если оно не существенно для действия.

Для аудио, например подкаста, нужна расшифровка.

Уточняющие вопросы

Чем субтитры (captions) отличаются от subtitles?

captions — для тех, кто не слышит: речь плюс звуки («звонит телефон»). subtitles — перевод речи для тех, кто слышит, но не знает языка.

Почему браузеры блокируют автовоспроизведение со звуком?

Внезапный звук мешает всем и перекрывает речь скринридера. Браузеры разрешают автозапуск только без звука или после действия пользователя.

Что прозвучало в вашем ответе
  • Названы субтитры через <track>.
  • Названа текстовая расшифровка или описание того, что видно только на экране.
  • Фоновое видео без звука, с паузой или с учётом prefers-reduced-motion.

Почитать на сайте

Спецификация: WCAG 2.2: субтитры

Доступность · Middle и выше

Дизайнер хочет выпадающий список городов с поиском и иконками, которые нельзя сделать в обычном <select>. Что придётся реализовать самому, чтобы список был доступен, и какие есть альтернативы?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему фокус не переводят на сами пункты списка?
  • Как проверить готовый компонент?

Разбор

Нативный <select> уже умеет всё: клавиатура, поиск по первой букве, скринридер, мобильный интерфейс выбора. Своя замена должна повторить это вручную:

  • роли и связи: поле role="combobox" с aria-expanded, aria-controls и aria-autocomplete="list"; всплывающий role="listbox" с пунктами role="option" и aria-selected;
  • клавиатура: стрелки вниз и вверх по пунктам, Enter выбирает, Escape закрывает и возвращает фокус, Home и End, ввод фильтрует;
  • активный пункт — фокус остаётся в поле, а текущий пункт указывает aria-activedescendant;
  • объявления: «Найдено 5 городов» через живую область;
  • мелочи: прокрутка к активному пункту, щелчок мимо закрывает, работа на сенсорных экранах и при увеличении.

Прежде чем писать: <input list="cities"> с <datalist> даёт подсказки без кода (оформить их нельзя); в новых браузерах <select> можно стилизовать через appearance: base-select; готовые библиотеки компонентов проверены со скринридерами. Иконки часто можно отказаться показывать в самом списке без потери смысла.

Уточняющие вопросы

Почему фокус не переводят на сами пункты списка?

Тогда человек не сможет продолжать печатать запрос. aria-activedescendant позволяет оставить фокус в поле и одновременно сообщать скринридеру текущий пункт.

Как проверить готовый компонент?

Пройти сценарий только с клавиатуры, затем с NVDA и VoiceOver, и на телефоне с TalkBack и VoiceOver: выбрать город, исправить выбор, закрыть без выбора.

Что прозвучало в вашем ответе
  • Назван шаблон combobox с ролями combobox, listbox, option.
  • Описана клавиатура: стрелки, Enter, Escape.
  • Назван aria-activedescendant или объявление результатов.
  • Названы альтернативы: <datalist>, стилизуемый <select> или библиотека.

Почитать на сайте

Спецификация: ARIA APG: шаблон combobox

Доступность · Junior

На кнопке написано «Купить», а aria-label содержит «Добавить товар в корзину». Почему голосовая команда «Нажать Купить» может не сработать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Нужно ли писать aria-label на каждую кнопку?
  • Чем имя отличается от описания?

Разбор

Пользователь голосового управления ориентируется на видимую надпись, а программа может искать элемент по доступному имени. Несовпадение мешает найти кнопку. Если текст кнопки достаточен, отдельный aria-label не нужен.

Для уточнения используйте имя, начинающееся с видимой подписи, например aria-label="Купить рюкзак". Дополнительное объяснение вроде условий доставки связывают через aria-describedby, не заменяя имя. Проверьте вычисленное имя в дереве доступности.

Уточняющие вопросы

Нужно ли писать aria-label на каждую кнопку?

Нет, нативная кнопка обычно получает имя из своего текста. Лишний атрибут может создать противоречие.

Чем имя отличается от описания?

Имя отвечает «что это за действие», описание добавляет условия и подсказки. Они не взаимозаменяемы.

Что прозвучало в вашем ответе
  • Объяснена замена доступного имени через aria-label.
  • Видимый текст включён в имя.
  • Дополнительная подсказка отделена от имени.

Почитать на сайте

Спецификация: Первоисточник: accessibility

Доступность · Junior

В форме две группы переключателей с ответами «Да» и «Нет». Как сделать понятным, на какой вопрос отвечает каждая группа?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Достаточно ли визуально поставить заголовок рядом?
  • Как отключить группу целиком?

Разбор

HTML
<fieldset>
  <legend>Нужна доставка?</legend>
  <label><input type="radio" name="delivery" value="yes">Да</label>
  <label><input type="radio" name="delivery" value="no">Нет</label>
</fieldset>

Другой вопрос получает другое name, иначе выбор во второй группе снимет ответ в первой. legend задаёт общий контекст, но не заменяет подписи отдельных вариантов. Рамку можно оформить CSS, сохранив семантику.

Уточняющие вопросы

Достаточно ли визуально поставить заголовок рядом?

Не всегда: семантическая группа связывает вопрос и варианты для вспомогательных технологий.

Как отключить группу целиком?

fieldset disabled отключает вложенные элементы управления, кроме находящихся внутри его первого legend.

Что прозвучало в вашем ответе
  • Выбраны fieldset и legend.
  • Каждый вариант имеет подпись.
  • Разные вопросы используют разные имена групп.

Почитать на сайте

Спецификация: Первоисточник: accessibility

Доступность · Middle и выше

Карточки задач можно переставлять только перетаскиванием мышью. Как дать тот же результат пользователям клавиатуры и людям, которым трудно удерживать указатель?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему недостаточно поменять CSS-свойство order?
  • Как проверить сценарий без перетаскивания?

Разбор

Одного клавиатурного сочетания недостаточно для людей, использующих указатель, но не способных выполнить жест перетаскивания. Кнопки или меню позиции дают альтернативу без удержания.

Порядок DOM должен следовать новому порядку карточек. На границах списка соответствующее действие отключается, фокус не теряется. В заранее существующую область role="status" можно вывести «Задача перенесена на позицию 2 из 5». Перетаскивание остаётся дополнительным способом.

Уточняющие вопросы

Почему недостаточно поменять CSS-свойство order?

Визуальное положение изменится, но порядок чтения и клавиатурного обхода может остаться прежним.

Как проверить сценарий без перетаскивания?

Переместить задачу только кнопками указателя, затем только клавиатурой; проверить границы, фокус и объявление результата.

Что прозвучало в вашем ответе
  • Предложена альтернатива без жеста удержания.
  • Альтернатива работает и с клавиатуры.
  • Сохранены логичный порядок DOM, фокус и сообщение о результате.

Спецификация: Первоисточник: accessibility

Доступность · Middle и выше

После перехода на другую страницу внутри SPA экран изменился, но скринридер продолжает читать старую ссылку, а название вкладки прежнее. Что нужно обновить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему недостаточно изменить только URL?
  • Нужно ли объявлять весь текст страницы живой областью?

Разбор

Используйте ссылки с реальными href, чтобы сохранялись открытие в новой вкладке и копирование адреса. После успешного перехода обновите document.title, текущий пункт навигации через aria-current="page" и фокус на заголовке с tabindex="-1" либо другом осмысленном элементе.

Не переносите фокус при каждом обновлении данных или фильтра: это не переход страницы. Возврат назад требует продуманного восстановления позиции и фокуса. При долгой загрузке или ошибке сообщите состояние, сохранив доступный путь продолжения.

Уточняющие вопросы

Почему недостаточно изменить только URL?

Адрес сам по себе не перемещает фокус и не сообщает новое название и содержимое при клиентской подмене страницы.

Нужно ли объявлять весь текст страницы живой областью?

Нет: это перегружает чтение. Достаточно понятного заголовка, стратегии фокуса и короткого состояния при необходимости.

Что прозвучало в вашем ответе
  • Обновлены название документа и текущий пункт навигации.
  • Предложена стратегия фокуса после перехода.
  • Сохранены реальные ссылки и поведение возврата.
  • Навигация отличена от фонового обновления данных.

Спецификация: Первоисточник: accessibility

Архитектура фронтенда · Junior

Страница каталога выросла до одного большого компонента: фильтры, товары, запросы и корзина. По каким признакам разделять её на части?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Обязан ли каждый выделенный компонент использоваться дважды?
  • Почему нельзя передать каждой карточке весь объект страницы?

Разбор

Фильтры описывают выбранные условия, список показывает товары, карточка представляет один товар. Связь с загрузкой и состоянием остаётся у осмысленного владельца; дочерним частям передают необходимые данные и действия.

Граница полезна, когда часть можно понять, изменить и проверить без чтения всей страницы. Не создавайте десятки обёрток, которые лишь переименовывают один тег. После разделения должно быть понятно, кто принимает решение, а кто только отображает результат.

Уточняющие вопросы

Обязан ли каждый выделенный компонент использоваться дважды?

Нет: самостоятельная ответственность и читаемость могут оправдывать компонент даже для одного места.

Почему нельзя передать каждой карточке весь объект страницы?

Контракт становится неявным и слишком широким. Карточка начинает зависеть от деталей, которые ей не нужны.

Что прозвучало в вашем ответе
  • Границы выбраны по обязанностям.
  • У частей определены входы и действия.
  • Владелец данных остаётся понятным после разделения.

Спецификация: React: разбиение интерфейса

Архитектура фронтенда · Junior

У общей кнопки появились параметры isCart, isProfile и isCheckout. Как сохранить переиспользование, не заставляя UI-компонент знать все страницы?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делать, если один экран требует уникального оформления?
  • Можно ли общей кнопке самой обращаться к API заказа?

Разбор

Вместо флагов страниц используйте осмысленные варианты дизайна и содержимое. Если оформление различается из-за задачи, назовите сам вариант, например основное или опасное действие. Проверку прав и отправку заказа выполняет вызывающий слой.

Не объединяйте несовместимые элементы лишь потому, что они похожи: ссылка для перехода и кнопка для действия имеют разные контракты. Композиция позволяет собрать бизнес-кнопку поверх общей, сохранив доступное имя, клавиатуру и состояния.

Уточняющие вопросы

Что делать, если один экран требует уникального оформления?

Оценить, является ли это новым общим вариантом или локальной композицией. Не добавлять флаг с названием страницы автоматически.

Можно ли общей кнопке самой обращаться к API заказа?

Для базового UI-компонента это лишняя бизнес-зависимость; действие передают снаружи.

Что прозвучало в вашем ответе
  • Общий UI отделён от бизнес-сценария.
  • Параметры описывают осмысленные варианты.
  • Сохранена семантика кнопки или ссылки.

Спецификация: React: композиция через props

Архитектура фронтенда · Junior

Правило скидки одинаково нужно в карточке, корзине и тесте. Сейчас каждый компонент считает его сам. Куда вынести правило и от чего оно должно зависеть?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему не вынести расчёт в хук только ради повторного использования?
  • Как проверять такую функцию?

Разбор

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

Если правило зависит от времени, передавайте нужную дату или часы явно, а не читайте текущее время внутри без возможности контроля. Клиентский расчёт служит интерфейсу, но окончательную стоимость подтверждает сервер. Общее правило не требует объединять всю связанную разметку.

Уточняющие вопросы

Почему не вынести расчёт в хук только ради повторного использования?

Если React-возможности не нужны, обычная функция проще и доступна другим окружениям.

Как проверять такую функцию?

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

Что прозвучало в вашем ответе
  • Правило выделено в функцию с явными входами.
  • Нет ненужной зависимости от UI или глобального store.
  • Расчёт отделён от форматирования и серверного подтверждения.

Спецификация: React: чистые вычисления

Архитектура фронтенда · Junior

API отдаёт цену строкой и дату в нестандартном поле. Компоненты по-разному преобразуют их и проверяют ошибки. Как организовать границу между API и интерфейсом?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Достаточно ли написать as Product[]?
  • Где выбирать текст ошибки для пользователя?

Разбор

Адаптер различает HTTP-ошибку, некорректный JSON и неподходящую структуру. Он переводит внешние поля в согласованные типы, сохраняя договорённость о единицах цены и часовом поясе.

Не превращайте любой отказ в пустой массив: «товаров нет» и «товары не удалось загрузить» — разные состояния. При изменении API адаптер локализует часть изменений, но не отменяет обновление контракта и проверок. Для теста можно подставить реализацию этой границы, явно указав, что транспорт тогда не проверяется.

Уточняющие вопросы

Достаточно ли написать as Product[]?

Нет: утверждение TypeScript не проверяет внешние данные во время работы.

Где выбирать текст ошибки для пользователя?

На уровне сценария интерфейса, опираясь на классифицированную ошибку; детали транспорта не обязаны становиться пользовательским текстом.

Что прозвучало в вашем ответе
  • Внешние данные проверяются на границе.
  • Преобразование централизовано в адаптере.
  • Ошибка загрузки не подменяется пустым результатом.

Спецификация: TypeScript: ограничения утверждений типов

Архитектура фронтенда · Junior

Модуль корзины импортирует каталог, каталог — пользовательский профиль, а профиль — корзину. Чем опасна такая связь и как начать её распутывать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Решит ли проблему общий файл, который переэкспортирует всё?
  • Когда помогает import type?

Разбор

Не любой цикл немедленно ломает программу, но чтение ещё не инициализированного экспорта может привести к ошибке. Простая замена импорта на динамический не устраняет неправильную ответственность.

Например, функция корзины может принимать идентификатор пользователя аргументом вместо импорта всего профиля. Общий тип товара может быть независимым контрактом. Публичные экспорты модуля делают намеренными, а доступ к внутренним файлам ограничивают. Не создавайте свалку shared, куда без разбора перемещены все зависимости.

Уточняющие вопросы

Решит ли проблему общий файл, который переэкспортирует всё?

Не обязательно: он может скрыть или создать дополнительные циклы. Нужно проверить реальный граф зависимостей.

Когда помогает import type?

Когда зависимость действительно нужна только для типов: такой импорт не создаёт runtime-связь. Архитектурный смысл зависимости всё равно нужно оценить.

Что прозвучало в вашем ответе
  • Названы риски инициализации и связанности.
  • Определены конкретные зависимости, а не только имена папок.
  • Предложены явные входы или независимый общий контракт.

Спецификация: TypeScript: модули

Архитектура фронтенда · Middle и выше

Изменение оформления заказа требует правок в общих папках компонентов, hooks, store и utils. Когда группировка по функции продукта полезнее группировки только по типу файла?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Когда код выносить в общий модуль?
  • Как предотвратить возврат глубоких импортов?

Разбор

Например, оформление заказа объединяет свои компоненты, модель и API-адаптер. Общие UI-примитивы и независимые сущности остаются отдельно. Другие модули обращаются через публичные экспорты, а не импортируют произвольные внутренние файлы.

Нужны правила: какие слои могут зависеть от каких, где собираются сценарии, что действительно является общим. Feature-Sliced Design — один из способов формализовать такие границы, а не обязательное название структуры любого проекта. Перенос файлов без ограничения связей сам по себе архитектуру не исправляет.

Уточняющие вопросы

Когда код выносить в общий модуль?

Когда у него есть самостоятельный стабильный смысл и несколько реальных потребителей, а не только похожие строки.

Как предотвратить возврат глубоких импортов?

Зафиксировать публичные точки входа и проверять ограничения импортов линтером или анализом зависимостей.

Что прозвучало в вашем ответе
  • Граница соответствует функции продукта.
  • Есть публичный контракт и скрытые внутренние детали.
  • Заданы правила зависимостей и критерии общего кода.

Спецификация: Feature-Sliced Design: slices и segments

Архитектура фронтенда · Middle и выше

Нужно сделать публичный каталог и личный кабинет. Как выбирать между статической генерацией, серверным и клиентским рендерингом, и откуда берутся ошибки hydration?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему SSR не гарантирует мгновенную интерактивность?
  • Стоит ли подавлять все hydration-предупреждения?

Разбор

Редко меняющиеся публичные страницы можно готовить заранее и кешировать. Персональные или часто меняющиеся данные могут требовать запроса к серверу либо клиентской загрузки; способы можно сочетать по маршрутам и областям.

Разные дата, случайное значение, локаль или чтение localStorage на первом клиентском рендере способны создать несовпадение с HTML сервера. Передавайте согласованные начальные данные и откладывайте браузерные различия по явной стратегии. SSR не убирает стоимость JavaScript и не разрешает хранить данные всех запросов в одном глобальном пользовательском store.

Уточняющие вопросы

Почему SSR не гарантирует мгновенную интерактивность?

Клиенту всё ещё может понадобиться загрузить и выполнить JavaScript и связать обработчики с интерфейсом.

Стоит ли подавлять все hydration-предупреждения?

Нет: сначала ищут различие исходных данных и рендера. Подавление для отдельных неизбежных различий не исправляет общий контракт.

Что прозвучало в вашем ответе
  • Сравнены свежесть, персонализация и стоимость.
  • Объяснено требование согласованного первого рендера.
  • Учтены JavaScript и изоляция серверных запросов.

Спецификация: React: hydrateRoot

Архитектура фронтенда · Middle и выше

Главная страница загружает редактор изображений, которым пользуются редко. Где провести границу ленивой загрузки и как избежать новой цепочки ожиданий?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что показать, если chunk не загрузился?
  • Почему много маленьких chunks не всегда лучше?

Разбор

Начните с анализа сборки: действительно ли редактор попадает в начальный пакет и какие зависимости дают размер. Динамический import() позволяет сборщику выделить отдельный chunk; в React для компонента можно использовать lazy с подходящей границей Suspense.

Необходимые для первого экрана части не стоит откладывать автоматически. При вероятном переходе возможна предварительная загрузка, но она расходует сеть. Проверьте время появления содержимого, отзывчивость и поведение после новой выкладки, когда старый клиент запросит уже удалённый chunk.

Уточняющие вопросы

Что показать, если chunk не загрузился?

Понятную ошибку и действие повтора или обновления страницы с сохранением важных данных; не бесконечный индикатор.

Почему много маленьких chunks не всегда лучше?

Появляются накладные расходы и цепочки зависимостей; решение оценивают по сетевому графу и реальному сценарию.

Что прозвучало в вашем ответе
  • Граница выбрана по независимой тяжёлой функции.
  • Есть загрузка и обработка отказа.
  • Проверены зависимости и реальные метрики вместо одного размера файла.

Спецификация: webpack: разделение кода

Архитектура фронтенда · Middle и выше

Старый кабинет нужно перенести на новую архитектуру, но разработка функций не должна остановиться. Как уменьшить риск по сравнению с заменой всего приложения за один релиз?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как выбрать первую область миграции?
  • Почему наличие feature flag ещё не гарантирует откат?

Разбор

Начните с карты зависимостей и одного самостоятельного сценария. Новая часть взаимодействует со старой через адаптер или согласованный API. Маршрут либо флаг включения направляет пользователей в нужную реализацию, а метрики показывают ошибки и успешное завершение сценария.

Не поддерживайте бесконечно две независимые копии изменяемых данных. Заранее определите владельца, совместимость форматов и условия удаления старого кода. Откат интерфейса должен учитывать, сможет ли старая версия читать уже записанные новые данные.

Уточняющие вопросы

Как выбрать первую область миграции?

Ограниченный полезный сценарий с понятными входами, измеримым результатом и небольшим числом связей.

Почему наличие feature flag ещё не гарантирует откат?

Данные, API или формат хранения могли измениться несовместимо; переключение флага не обращает эти изменения автоматически.

Что прозвучало в вашем ответе
  • Выбрана ограниченная область за стабильной границей.
  • Есть проверка поведения, наблюдаемость и откат.
  • Учтены владельцы данных и совместимость форматов.

Спецификация: Azure Architecture: постепенная замена системы

Архитектура фронтенда · Middle и выше

Команда спорит, нужен ли отдельный store или достаточно локального состояния и кеша запросов. Как принять решение так, чтобы его можно было пересмотреть через полгода?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Нужно ли менять решение после появления более популярной библиотеки?
  • Что даёт небольшой прототип?

Разбор

Опишите потребителей состояния, сложность переходов, требования к отладке, SSR, persistence, тестированию и знаниям команды. Сравните несколько подходов на характерном сценарии, включая вариант без новой зависимости.

В коротком ADR сохраните контекст, решение, альтернативы и принятые компромиссы. Добавьте признаки, при которых выбор перестанет подходить: например, рост межэкранных обновлений или проблемы подписок. Это не обещание универсально лучшей архитектуры и не документ на десятки страниц.

Уточняющие вопросы

Нужно ли менять решение после появления более популярной библиотеки?

Само по себе это не причина. Важны изменившиеся требования, измеримые проблемы или стоимость поддержки.

Что даёт небольшой прототип?

Он проверяет наиболее рискованные предположения на реальном сценарии, но его удачный демопоказ не заменяет оценку эксплуатации.

Что прозвучало в вашем ответе
  • Выбор связан с требованиями и ограничениями.
  • Сравнены альтернативы и последствия.
  • Решение записано вместе с условиями пересмотра.

Спецификация: AWS: архитектурные решения

Тестирование: unit и интеграционное · Junior

В корзине есть расчёт скидки, форма промокода и оформление заказа. Что проверить unit-тестом, интеграционным тестом и сквозным сценарием?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Можно ли назвать любой тест в браузере сквозным?
  • Почему не проверить всё только сквозными тестами?

Разбор

Для расчёта проверяют входные данные и ожидаемую сумму без DOM и сети. Для формы вводят промокод, отправляют её и проверяют итог и ошибки, оставляя реальные связи между компонентами. Сетевую границу можно подменить управляемыми ответами.

Сквозной тест проходит оформление в браузере с тестовым сервером и данными. Внешнюю оплату можно заменить sandbox или заглушкой, явно обозначив границу. Названия уровней в командах различаются: важнее сказать, что реально проверено и что подменено.

Уточняющие вопросы

Можно ли назвать любой тест в браузере сквозным?

Нет: браузерный тест отдельного компонента может оставаться компонентным или интеграционным. Среда запуска сама по себе не задаёт охват.

Почему не проверить всё только сквозными тестами?

Они дороже и сложнее диагностируются. Правила расчёта быстрее проверить отдельно, а сквозные тесты оставить для важных связей и путей.

Что прозвучало в вашем ответе
  • Для расчёта выбран изолированный тест.
  • Интеграционный тест проверяет реальные связи нескольких частей.
  • У сквозного сценария обозначены инфраструктура и подмены.

Спецификация: Redux: интеграционный подход к тестам

Тестирование: unit и интеграционное · Junior

Скидка 10% действует при сумме от 1000 рублей включительно. Какие проверки полезнее десяти случайных сумм и как получить ожидаемый результат?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему важно значение ровно 1000?
  • Нужно ли повторять формулу функции в ожидаемом значении?

Разбор

При целых копейках примеры: 99900 копеек остаются 99900, 100000 превращаются в 90000, 101000 — в 90900. Эти эталоны взяты из правила, а не из повторного вызова проверяемой функции.

Тест разделяют на подготовку, действие и проверку. Отдельно решают, что происходит с отрицательной суммой и как округляется дробная копейка: тест должен защищать договорённость, а не случайное поведение текущей реализации.

Уточняющие вопросы

Почему важно значение ровно 1000?

Оно различает условия >= и > и ловит ошибку включённости границы.

Нужно ли повторять формулу функции в ожидаемом значении?

Нет: одинаковая ошибка может оказаться и в реализации, и в тесте. Для небольшого набора используют независимо рассчитанные результаты.

Что прозвучало в вашем ответе
  • Названы значения по обе стороны порога и на нём.
  • Ожидаемые результаты рассчитаны независимо.
  • Определены единицы денег и правило округления.

Спецификация: Vitest: написание тестов

Тестирование: unit и интеграционное · Junior

Тест формы проверяет вызов внутреннего setError, но после рефакторинга ломается, хотя пользователь по-прежнему видит ошибку. Что лучше проверять?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Когда проверка вызова функции уместна?
  • Почему поиск только по CSS-классу хрупок?

Разбор

Найдите поле по подписи и кнопку по роли и имени, введите неверное значение и отправьте форму. Проверьте текст ошибки, связь с полем и отсутствие успешного результата. Затем исправьте ввод и убедитесь, что форма продолжает работать.

Не обращайтесь к экземпляру компонента и его внутренним переменным без необходимости. Такая проверка допускает смену хука или структуры компонентов, но обнаруживает исчезнувшее сообщение.

Уточняющие вопросы

Когда проверка вызова функции уместна?

Когда вызов — часть публичного контракта, например переданного onSubmit с оговорёнными данными или внешнего адаптера.

Почему поиск только по CSS-классу хрупок?

Класс может измениться ради оформления. Роль и доступное имя ближе к способу, которым пользователь находит элемент.

Что прозвучало в вашем ответе
  • Проверяется поведение после пользовательского действия.
  • Элементы найдены по роли или подписи.
  • Внутренняя реализация не закреплена без необходимости.

Спецификация: Testing Library: принципы

Тестирование: unit и интеграционное · Junior

Тест нажимает «Загрузить» и сразу ищет результат через getByText. Иногда он падает. Как дождаться результата без фиксированной паузы?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему увеличение паузы не решает проблему надёжно?
  • Как проверить, что индикатор загрузки исчез?

Разбор

В Testing Library ожидайте await user.click(button), затем await screen.findByRole("heading", { name: "Результаты" }). getBy ищет сразу и бросает ошибку при отсутствии; queryBy удобен для проверки отсутствия; findBy повторяет поиск до успеха или тайм-аута.

Для других асинхронных условий есть waitFor с проверкой, которая бросает ошибку до выполнения условия. Не помещайте повторяемое действие пользователя внутрь waitFor: callback может выполниться много раз.

Уточняющие вопросы

Почему увеличение паузы не решает проблему надёжно?

На медленном окружении её всё равно может не хватить, а на быстром она зря замедлит каждый запуск.

Как проверить, что индикатор загрузки исчез?

Дождаться его удаления или повторять проверку отсутствия; это проверка состояния, а не задержка наугад.

Что прозвучало в вашем ответе
  • Асинхронные действия и поиск ожидаются.
  • Различены getBy, queryBy и findBy.
  • Повторяемое ожидание не содержит пользовательского действия.

Спецификация: Testing Library: запросы

Тестирование: unit и интеграционное · Junior

Тест проходит отдельно, но падает после другого теста. Оба используют общий store и меняют время. С чего начать исправление?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Поможет ли принудительно запускать тесты в нужном порядке?
  • Всегда ли достаточно очистить историю вызовов мока?

Разбор

Тест не должен зависеть от порядка выполнения. Общий singleton-store может сохранить пользователя или ошибку предыдущего сценария; аналогично ведут себя кеш запросов, storage и глобальные моки.

Создавайте окружение в подготовке теста, а восстановление обеспечивайте даже при падении проверки. Для параллельных браузерных сценариев нужны отдельные аккаунты или уникальные данные, если операции меняют сервер. Повторный запуск полезен для диагностики, но не устраняет зависимость.

Уточняющие вопросы

Поможет ли принудительно запускать тесты в нужном порядке?

Это скрывает связь. Лучше сделать сценарии независимыми или явно оформить общий сценарий одним тестом.

Всегда ли достаточно очистить историю вызовов мока?

Нет: история вызовов и подменённая реализация — разные вещи. Нужно восстановить именно то состояние, которое менял тест.

Что прозвучало в вашем ответе
  • Найдены возможные общие store, кеши и глобальные подмены.
  • Окружение создаётся независимо для каждого теста.
  • Очистка выполняется и при неуспешной проверке.

Спецификация: Vitest: подмены и очистка

Тестирование: unit и интеграционное · Middle и выше

Нужно проверить форму сохранения профиля вместе со store и API-клиентом. Что оставить настоящим, где подменить сеть и какие исходы проверить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как не получить ложный успех на неожиданном запросе?
  • Нужен ли настоящий сервер в каждом таком тесте?

Разбор

Можно использовать MSW для управляемых ответов HTTP. Тест заполняет форму и отправляет её обычным действием; обработчик сети проверяет метод и полезную нагрузку. Затем интерфейс должен отразить результат и снять состояние загрузки.

Отдельные сценарии — ошибка проверки, сетевой отказ и повтор после исправления. Не подменяйте одновременно хук загрузки, store и дочерний компонент: иначе связи, ради которых нужен интеграционный тест, исчезнут. Подмена сети не доказывает совместимость с настоящим сервером; её проверяют отдельно.

Уточняющие вопросы

Как не получить ложный успех на неожиданном запросе?

Настроить тестовое окружение так, чтобы необработанные запросы считались ошибкой, кроме явно разрешённых.

Нужен ли настоящий сервер в каждом таком тесте?

Нет, если проверяется клиентская интеграция. Для контракта и развёрнутой системы нужны отдельные проверки с сервером.

Что прозвучало в вашем ответе
  • Сохранены реальные связи интерфейса, store и API-клиента.
  • Сеть подменена на явной границе.
  • Проверены успех и отказ с сохранением ввода.
  • Назван предел проверки с подменённым сервером.

Спецификация: MSW: подмена сетевой границы

Тестирование: unit и интеграционное · Middle и выше

Поиск ждёт 300 мс после последнего ввода. Как проверить debounce быстро и убедиться, что закрытая форма не отправит отложенный запрос?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему runAllTimers опасен для бесконечного интервала?
  • Проверяет ли такой тест реальную скорость сервера?

Разбор

После первого ввода продвиньте часы на 299 мс и убедитесь, что запроса нет. Затем имитируйте новый ввод, снова выдержите 299 мс без запроса и ещё 1 мс: должен уйти запрос для последнего значения. Обновления React и промисы нужно корректно ожидать средствами тестового окружения.

В сценарии закрытия создайте ожидающий таймер, размонтируйте форму и продвиньте время: запроса быть не должно. После теста восстановите реальные таймеры. При использовании user-event согласуйте его собственные задержки с продвижением тестовых часов.

Уточняющие вопросы

Почему runAllTimers опасен для бесконечного интервала?

Он может запускать постоянно создаваемые таймеры. Лучше продвигать конкретный интервал или выполнять только ожидаемую работу.

Проверяет ли такой тест реальную скорость сервера?

Нет: он проверяет планирование и отмену работы, а не производительность сети.

Что прозвучало в вашем ответе
  • Проверено отсутствие запроса до порога.
  • После нового ввода задержка начинается заново.
  • Проверена отмена при закрытии и восстановлены часы.

Спецификация: Vitest: управление таймерами

Тестирование: unit и интеграционное · Middle и выше

Отчёт показывает 100% покрытия строк функции оплаты, но тесты не замечают неверную сумму. Что означает покрытие и как оценить качество проверок?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему исполненная ветка может остаться непроверенной по смыслу?
  • Стоит ли одинаково тестировать расчёт оплаты и декоративную обёртку?

Разбор

Тест может вызвать функцию и не проверить сумму вообще. Покрытие ветвей полезнее одного покрытия строк для поиска пропущенных условий, но также не гарантирует проверку всех сочетаний входов.

Сначала перечислите риски: повторное списание, неверное округление, отказ и частичный результат. Проверьте, падают ли тесты при осмысленной ошибке в реализации; автоматизировать такую оценку помогает mutation testing. Метрику используют как подсказку для ревью, а не как самостоятельную цель.

Уточняющие вопросы

Почему исполненная ветка может остаться непроверенной по смыслу?

Код выполнился, но итог мог не участвовать ни в одном утверждении или ожидание повторяет ту же ошибочную формулу.

Стоит ли одинаково тестировать расчёт оплаты и декоративную обёртку?

Приоритет зависит от последствий ошибки и сложности поведения, а не от одинакового процента для каждого файла.

Что прозвучало в вашем ответе
  • Покрытие отделено от правильности ожиданий.
  • Названы важные границы и ветви.
  • Предложена проверка способности теста обнаружить ошибку.

Спецификация: Vitest: покрытие кода

Тестирование: unit и интеграционное · Middle и выше

Тест компонента в DOM-эмуляторе проходит, но в браузере выпадающая панель обрезается и недоступна с клавиатуры. Какие проверки нужно перенести в настоящий браузер?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что оставить в быстрых тестах без браузера?
  • Нужен ли скриншот для проверки клавиатуры?

Разбор

В браузере откройте панель клавиатурой, пройдите её элементы и закройте Escape. Проверьте видимость, границы экрана, фокус после закрытия и узкий viewport. Локаторы по ролям и ожидающие проверки уменьшают зависимость от разметки и скорости.

Скриншот помогает обнаружить визуальное изменение, но сам по себе не доказывает доступность. Не пытайтесь исправить отсутствие layout в эмуляторе случайными координатами: такая подмена лишь проверяет собственную заглушку.

Уточняющие вопросы

Что оставить в быстрых тестах без браузера?

Чистые расчёты, преобразования данных и состояния, которым не нужны измерения раскладки.

Нужен ли скриншот для проверки клавиатуры?

Нет, нужно выполнить клавиатурный сценарий и проверить фокус; изображение не показывает доступность действия.

Что прозвучало в вашем ответе
  • Названы ограничения DOM-эмулятора.
  • Проверены реальная видимость и клавиатурный сценарий.
  • Визуальная проверка не подменяет проверку поведения.

Спецификация: Playwright: практики браузерных тестов

Тестирование: unit и интеграционное · Middle и выше

Интеграционные тесты зелёные, но после обновления бэкенда форма падает: мок возвращает старое поле name, сервер — displayName. Как обнаруживать такие расхождения?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Достаточно ли TypeScript-типа ответа?
  • Заменяют ли контрактные тесты сквозной сценарий?

Разбор

Опишите обязательные поля, допустимые значения и ошибки. Генерация типов из схемы помогает находить часть изменений при сборке, но тип не проверяет фактический JSON. На границе клиента можно валидировать данные и преобразовывать их в модель интерфейса.

Контрактные тесты проверяют ожидания потребителя на поставщике API. Даже без специальной платформы полезна небольшая проверка ключевых ответов тестового сервера по схеме. Управляемые моки остаются для ошибок и редких состояний; успешный мок не гарантирует совместимость развёрнутых версий.

Уточняющие вопросы

Достаточно ли TypeScript-типа ответа?

Нет: во время работы сервер может прислать другую структуру, а типы уже удалены.

Заменяют ли контрактные тесты сквозной сценарий?

Нет, они проверяют договорённость между частями, а не весь пользовательский путь и окружение.

Что прозвучало в вашем ответе
  • Определён единый контракт данных и ошибок.
  • Моки сверяются с контрактом.
  • Проверка типов отделена от реального JSON и проверки поставщика.

Спецификация: Pact: контрактное тестирование

React · Junior

Компонент получает товар через props, но пользователь может раскрыть его описание. Какие данные должны приходить снаружи, а какие можно хранить в компоненте?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сообщить родителю о выборе товара?
  • Нужно ли хранить итоговую цену отдельно, если она получается из цены и количества?

Разбор

Название и цена приходят от владельца данных. Флаг раскрытия можно хранить через useState, если он нужен только карточке. Нельзя менять объект товара из props: это изменит общие данные без явного обновления.

Если родителю нужно закрывать все карточки, он хранит состояние и передаёт значение вместе с обработчиком изменения. Не копируйте props в state без причины: копия перестанет следовать последующим изменениям.

Уточняющие вопросы

Как сообщить родителю о выборе товара?

Вызвать переданный обработчик, например onSelect(product.id), а изменение данных оставить владельцу.

Нужно ли хранить итоговую цену отдельно, если она получается из цены и количества?

Обычно нет: её вычисляют из исходных данных, чтобы не синхронизировать несколько копий.

Что прозвучало в вашем ответе
  • Props не изменяются компонентом.
  • Локальное состояние выбрано по области использования.
  • Общее состояние имеет одного владельца.

Спецификация: React: props и состояние

React · Junior

В одном обработчике трижды вызывается setCount(count + 1), но счётчик растёт только на один. Почему и как увеличить его на три последовательными обновлениями?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что выведет чтение count сразу после setCount?
  • Можно ли в функции обновления отправить аналитику?

Разбор

Если count равен 0, три вызова setCount(count + 1) ставят в очередь замену на 1. Значение переменной внутри уже выполняющегося обработчика не меняется.

Функциональное обновление описывает переход из предыдущего состояния. Обновляющая функция должна быть чистой: в ней нельзя отправлять запросы или уведомления. Это правило очереди состояния, а не совет добавлять задержку между вызовами.

Уточняющие вопросы

Что выведет чтение count сразу после setCount?

Значение из того рендера, которому принадлежит обработчик. Новое значение будет доступно при следующем рендере.

Можно ли в функции обновления отправить аналитику?

Нет: React может вызывать её повторно для проверки чистоты. Аналитику действия размещают в обработчике события.

Что прозвучало в вашем ответе
  • Назван снимок состояния текущего рендера.
  • Предложено функциональное обновление.
  • Функция обновления не содержит побочных эффектов.

Спецификация: React: очередь обновлений

React · Junior

После сортировки списка текст в полях карточек оказался у других товаров. В качестве key использован индекс. Как ключ связан с состоянием компонента?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему Math.random() не подходит для ключа?
  • Когда индекс не создаёт такого риска?

Разбор

При сортировке элемент на позиции 0 становится другим товаром, но ключ 0 остаётся прежним. React может сохранить состояние поля на этой позиции. Используйте key={product.id}.

Ключ должен быть стабильным, уникальным среди соседей и не генерироваться случайно при рендере. Смена ключа намеренно пересоздаёт поддерево и сбрасывает состояние. Сам key не передаётся компоненту как обычный prop: идентификатор передают отдельно.

Уточняющие вопросы

Почему Math.random() не подходит для ключа?

Ключ каждый раз меняется: компоненты пересоздаются, теряют состояние и фокус.

Когда индекс не создаёт такого риска?

В действительно статичном списке без перестановки, удаления и добавления; стабильный ID всё равно предпочтительнее при его наличии.

Что прозвучало в вашем ответе
  • Ключ связан с идентичностью элемента, а не порядком отображения.
  • Для товаров выбран стабильный ID.
  • Объяснены последствия случайного или изменившегося ключа.

Спецификация: React: ключи списков

React · Junior

Поле React получает value={name}, но не даёт печатать. Как устроено управляемое поле и когда вместо value нужен defaultValue?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как сделать поле намеренно только для чтения?
  • Как прочитать неуправляемую форму при отправке?

Разбор

Для текстового поля используйте строковое начальное состояние и пару value={name}, onChange={event => setName(event.target.value)}. Для флажка управляют checked, а не текстовым value.

Без обновления состояния React возвращает заданное значение после ввода. Не переключайте поле неожиданно из неуправляемого в управляемое: например, заменой undefined на строку после загрузки. Подпись label нужна при обоих подходах.

Уточняющие вопросы

Как сделать поле намеренно только для чтения?

Указать readOnly и объяснить его назначение; отсутствие обработчика не заменяет явный контракт.

Как прочитать неуправляемую форму при отправке?

Через FormData у формы; у полей должны быть имена name.

Что прозвучало в вашем ответе
  • Связаны значение и обработчик обновления.
  • defaultValue отличён от текущего управляемого значения.
  • Для флажка названо свойство checked.

Спецификация: React: input

React · Junior

Нужно хранить идентификатор таймера и показывать число оставшихся секунд. Что положить в useRef, а что в useState?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Подойдёт ли обычная переменная в теле компонента?
  • Когда безопасно обращаться к DOM через ref?

Разбор

Ref сохраняет изменяемое значение между рендерами и позволяет обратиться к DOM-элементу. Но React не отслеживает изменения current для обновления интерфейса. Если время должно меняться на экране, обновляют state.

При создании таймера в эффекте очистка отменяет его. Если ID нужен только этой очистке, достаточно локальной переменной внутри эффекта; ref нужен, когда к ID обращаются другие обработчики. Не храните в ref скрытую копию отображаемого состояния.

Уточняющие вопросы

Подойдёт ли обычная переменная в теле компонента?

Она создаётся заново при каждом вызове компонента и не является устойчивым хранилищем между рендерами.

Когда безопасно обращаться к DOM через ref?

После commit, например в обработчике события или подходящем эффекте; при рендере DOM может ещё не существовать.

Что прозвучало в вашем ответе
  • Отображаемое значение хранится в state.
  • Изменение ref не вызывает рендер.
  • Для таймера предусмотрена очистка.

Спецификация: React: ref и state

React · Middle и выше

Компонент хранит список товаров и строку поиска, а отфильтрованный список записывает в state через эффект. Что здесь можно упростить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как проверить, нужна ли мемоизация?
  • Когда копия данных в локальном состоянии оправдана?

Разбор

Вычислите const visible = products.filter(matchesQuery) из актуальных props и state. Если фильтрация действительно дорогая, можно применить useMemo с полными зависимостями, но корректность не должна зависеть от кеша.

Эффект нужен для синхронизации с внешней системой: подпиской, таймером или сторонним виджетом. Действие пользователя, например покупку, выполняют в его обработчике. Производное значение не становится внешней системой только потому, что зависит от состояния.

Уточняющие вопросы

Как проверить, нужна ли мемоизация?

Измерить стоимость вычисления и задержку сценария на реалистичных данных, затем сравнить результат оптимизации.

Когда копия данных в локальном состоянии оправдана?

Например, независимый редактируемый черновик. Тогда явно задают момент его создания, сброса и сохранения.

Что прозвучало в вашем ответе
  • Производные данные вычисляются без эффекта.
  • Объяснены лишний рендер и риск рассогласования.
  • Мемоизация отделена от корректности логики.

Спецификация: React: когда эффект не нужен

React · Middle и выше

Компонент подписывается на чат по roomId. После смены комнаты сообщения приходят из старой, а при возвращении дублируются. Как организовать эффект?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему опасно подавить предупреждение линтера о зависимости?
  • Почему нельзя просто сделать функцию эффекта async?

Разбор

Внутри эффекта создайте подключение, подключите его и верните функцию отключения. Все прочитанные props, state и другие реактивные значения учитываются в зависимостях. Объект настроек часто проще создать внутри эффекта, чем делать его новым зависимым объектом на каждом рендере.

Очистка выполняется также при размонтировании. Асинхронный результат старой подписки не должен менять состояние новой комнаты: нужен механизм отмены или проверки актуальности. Пустой массив зависимостей не исправляет устаревшее замыкание.

Уточняющие вопросы

Почему опасно подавить предупреждение линтера о зависимости?

Эффект может продолжить использовать значения прежнего рендера и перестать следовать реальным входным данным.

Почему нельзя просто сделать функцию эффекта async?

Она вернёт промис вместо функции очистки. Асинхронную работу запускают внутри эффекта и отдельно возвращают синхронную очистку.

Что прозвучало в вашем ответе
  • Подписка связана с текущим roomId.
  • Возвращается очистка конкретного подключения.
  • Учтены зависимости и устаревшие асинхронные результаты.

Спецификация: React: жизненный цикл эффектов

React · Middle и выше

При разработке под StrictMode настройка эффекта выполняется, очищается и выполняется снова. Коллега предлагает убрать StrictMode. Что стоит проверить сначала?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как распознать мутацию при рендере?
  • Является ли дополнительная проверка гарантией отсутствия утечек?

Разбор

Когда Strict Mode включён на корне приложения, React в разработке дополнительно проверяет эффекты циклом setup → cleanup → setup. Также повторные вызовы функций, которые должны быть чистыми, выявляют мутации во время рендера. Это не означает, что все действия пользователя в production выполняются дважды.

Подписка должна отключаться, таймер — отменяться. Покупку или отправку формы нельзя переносить в эффект монтирования: это действие конкретного события. Ref-флаг «уже запускали» может скрыть ошибку очистки вместо её исправления.

Уточняющие вопросы

Как распознать мутацию при рендере?

Например, компонент добавляет элемент в массив из props, и повторный вызов добавляет его ещё раз. Нужно создать новую структуру без изменения входа.

Является ли дополнительная проверка гарантией отсутствия утечек?

Нет, она помогает обнаружить часть ошибок; реальные сценарии открытия, закрытия и смены параметров всё равно проверяют.

Что прозвучало в вашем ответе
  • Дополнительный цикл отнесён к проверкам разработки.
  • Проверены чистота рендера и симметрия очистки.
  • Действия пользователя не запускаются из эффекта монтирования.

Спецификация: React: StrictMode

React · Middle и выше

Список тормозит при вводе, и предлагается обернуть все компоненты в memo, а функции в useCallback. Как проверить, что оптимизация поможет?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Остановит ли memo обновление потребителя Context?
  • Что опасного в своей функции сравнения props?

Разбор

Проверьте объём списка, расположение состояния ввода, стоимость расчётов и необходимость виртуализации. По умолчанию memo сравнивает каждый prop через Object.is: новый объект или функция могут нарушить равенство.

useCallback сохраняет ссылку на функцию, а не ускоряет её выполнение. useMemo кеширует результат вычисления. Их применение имеет цену и требует корректных зависимостей. Если в проекте включён React Compiler, часть мемоизации выполняется автоматически; решение всё равно оценивают измерениями.

Уточняющие вопросы

Остановит ли memo обновление потребителя Context?

Нет: изменение используемого контекста может вызвать рендер независимо от равенства props.

Что опасного в своей функции сравнения props?

Если проигнорировать изменившийся обработчик или другие значимые данные, компонент продолжит использовать устаревшие значения. Само сравнение тоже может быть дорогим.

Что прозвучало в вашем ответе
  • Оптимизация начинается с измерения.
  • Различены назначения memo, useMemo и useCallback.
  • Учтены ссылки props, state и Context.

Спецификация: React: memo и профилирование

React · Middle и выше

Ошибка рендера виджета обрушила весь экран. Как ограничить область сбоя и поймает ли Error Boundary ошибку запроса в обработчике кнопки?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Нужна ли граница вокруг каждой кнопки?
  • Что показать при ошибке загрузки данных?

Разбор

Границы размещают вокруг самостоятельных областей: например, график может отказать, а фильтры и навигация остаются доступны. Запасной интерфейс сообщает о проблеме и даёт осмысленный повтор; ошибку отправляют в мониторинг.

При повторе важно сбросить состояние границы и устранить причину или заново получить данные. Ошибка самой границы передаётся выше. Обработчик сохранения отдельно ловит сетевой отказ и сохраняет введённые данные.

Уточняющие вопросы

Нужна ли граница вокруг каждой кнопки?

Обычно нет: выбирают области, которые могут независимо восстановиться, а не дробят интерфейс без сценария.

Что показать при ошибке загрузки данных?

Явное состояние ошибки с возможностью повторить запрос; оно не обязано возникать через исключение при рендере.

Что прозвучало в вашем ответе
  • Определена область изоляции сбоя.
  • Ошибки рендера отличены от ошибок обработчиков и запросов.
  • Предусмотрены запасной интерфейс, повтор и диагностика.

Спецификация: React: Error Boundary

React · Junior

Когда React перерисовывает компонент? Что такое «виртуальный DOM» и почему повторный рендер ещё не означает изменения страницы?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Сбросится ли состояние поля, если вместо <div> вокруг него на том же месте стал <section>?
  • Медленный ли повторный рендер сам по себе?

Разбор

Причины повторного рендера:

  • вызван setState с новым значением;
  • изменилось значение контекста, который компонент читает через useContext;
  • перерисовался родитель — по умолчанию вместе с ним рендерятся все дочерние компоненты, даже если их props не изменились.

Рендер — это только вызов функции компонента: она возвращает дерево элементов, лёгких объектов с описанием интерфейса. Это описание иногда называют виртуальным DOM. React сравнивает новое дерево с предыдущим и применяет к настоящему DOM минимальные изменения: если текст и атрибуты те же, DOM не трогается.

При сравнении элемент другого типа на том же месте пересоздаётся целиком вместе с состоянием, а в списках элементы сопоставляются по key.

Уточняющие вопросы

Сбросится ли состояние поля, если вместо <div> вокруг него на том же месте стал <section>?

Да: тип элемента на этой позиции изменился, React удалит старое поддерево и создаст новое, состояние внутри потеряется.

Медленный ли повторный рендер сам по себе?

Обычно нет: вызов функций и сравнение дешевле работы с DOM. Проблемы начинаются, когда рендер тяжёлый или затрагивает тысячи компонентов, — это видно в профайлере.

Что прозвучало в вашем ответе
  • Названы причины рендера: состояние, контекст, рендер родителя.
  • Рендер отличён от изменения DOM.
  • Объяснено сравнение деревьев и роль типа элемента и key.

Спецификация: React: Render and Commit

React · Junior

Фильтр каталога и список товаров — соседние компоненты. Фильтр хранит выбранную категорию у себя, а списку она тоже нужна. Как передать её списку?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему не синхронизировать две копии состояния через useEffect?
  • Что такое prop drilling и когда он действительно проблема?

Разбор

Соседи не могут читать состояние друг друга. Общий родитель, например Catalog, хранит category через useState и передаёт:

  • фильтру — value={category} и onChange={setCategory};
  • списку — category={category}, по которой он фильтрует товары.

Фильтр становится управляемым: он не хранит своё значение, а показывает то, что пришло, и сообщает о выборе. У каждого значения один источник правды, поэтому фильтр и список не рассогласуются.

Если категорию нужно передать через много уровней, рассматривают композицию (передачу готовых элементов через children) или Context, а для сохранения в ссылке — параметры адреса.

Уточняющие вопросы

Почему не синхронизировать две копии состояния через useEffect?

Две копии расходятся хотя бы на один рендер, а эффекты добавляют лишние циклы обновления. Проще иметь одно значение у владельца.

Что такое prop drilling и когда он действительно проблема?

Передача props через компоненты, которым они не нужны. Два-три уровня — нормально; при глубокой цепочке помогают композиция и Context.

Что прозвучало в вашем ответе
  • Состояние поднято к общему родителю.
  • Названы передача значения через props и изменения через колбэк.
  • Упомянут единый источник правды.

Спецификация: React: Sharing State Between Components

React · Junior

В трёх компонентах повторяется одинаковый код: состояние «онлайн/офлайн» и подписка на события online и offline. Как вынести его в свой хук и какие правила хуков нужно соблюдать?

                        function StatusIndicator() {
  const [online, setOnline] = useState(navigator.onLine);
  useEffect(() => {
    const update = () => setOnline(navigator.onLine);
    window.addEventListener('online', update);
    window.addEventListener('offline', update);
    return () => {
      window.removeEventListener('online', update);
      window.removeEventListener('offline', update);
    };
  }, []);
  return online ? 'Online' : 'Offline';
}
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему нельзя вызвать хук внутри if?
  • Нужно ли называть хуком функцию, которая не вызывает хуков?

Разбор

JavaScript
function useOnlineStatus() {
  const [online, setOnline] = useState(navigator.onLine);
  useEffect(() => {
    const update = () => setOnline(navigator.onLine);
    window.addEventListener('online', update);
    window.addEventListener('offline', update);
    return () => {
      window.removeEventListener('online', update);
      window.removeEventListener('offline', update);
    };
  }, []);
  return online;
}

Свой хук — обычная функция с именем на use, которая вызывает другие хуки. Компонент пишет const online = useOnlineStatus() и не знает о подписках.

Важно:

  • каждый вызов хука создаёт отдельное состояние — два компонента не делят одно значение, как и при прямом использовании useState;
  • подписка в эффекте обязательно снимается в функции очистки;
  • правила хуков: вызывать на верхнем уровне, в одном и том же порядке при каждом рендере, только из компонентов и других хуков. React различает хуки по порядку вызова.

Префикс use нужен, чтобы линтер проверял правила хуков внутри функции.

Уточняющие вопросы

Почему нельзя вызвать хук внутри if?

При другом результате условия изменится порядок вызовов, и React сопоставит состояние не с тем хуком.

Нужно ли называть хуком функцию, которая не вызывает хуков?

Нет, это обычная функция-утилита. Префикс use ставят, только если внутри есть хуки.

Что прозвучало в вашем ответе
  • Логика вынесена в функцию с префиксом use.
  • Подписка снимается в функции очистки.
  • Сказано, что состояние у каждого вызова своё.
  • Названы правила хуков: верхний уровень, постоянный порядок.

Спецификация: React: Reusing Logic with Custom Hooks

React · Junior

Профиль загружается в useEffect по userId. При быстром переключении пользователей иногда показывается чужой профиль. Почему и как исправить?

                        useEffect(() => {
  const controller = new AbortController();
  fetch('/api/users/' + userId, { signal: controller.signal })
    .then((response) => response.json())
    .then(setUser)
    .catch((error) => {
      if (error.name !== 'AbortError') setError(error);
    });
  return () => controller.abort();
}, [userId]);
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему в разработке запрос уходит дважды?
  • Можно ли сделать саму функцию эффекта async?

Разбор

При смене userId React выполняет очистку прошлого эффекта и запускает новый. Без очистки оба запроса живут, и тот, что завершился последним, записывает свой результат — даже если он устарел.

Очистка с controller.abort() отменяет прошлый запрос, а ошибку AbortError не показывают пользователю. Вариант попроще — флаг let ignore = false, который очистка ставит в true.

Ещё нужны состояния загрузки и ошибки и проверка response.ok. Кеширование, повтор и предзагрузку руками писать долго, поэтому используют TanStack Query, SWR или загрузку данных фреймворка.

Уточняющие вопросы

Почему в разработке запрос уходит дважды?

StrictMode в разработке монтирует компонент, очищает эффект и монтирует снова. С правильной очисткой первый запрос отменяется, в production такого нет.

Можно ли сделать саму функцию эффекта async?

Нет: эффект должен вернуть функцию очистки, а не промис. Асинхронную функцию объявляют внутри эффекта и вызывают.

Что прозвучало в вашем ответе
  • Названа гонка ответов.
  • Устаревший запрос отменяется или игнорируется в очистке.
  • Упомянуты состояния загрузки и ошибки.

Спецификация: React: Fetching data with Effects

React · Junior

Код {count && <Badge count={count} />} при нуле товаров выводит на странице «0». Почему и как правильно выводить элемент по условию?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем скрытие через условие отличается от CSS display: none?
  • Что вернуть из компонента, если показывать нечего?

Разбор

0 && x равно 0. React не рисует false, true, null и undefined, но число и строку выводит. Поэтому появляется «0».

Способы условного вывода:

  • {count > 0 && <Badge … />} — явное логическое условие;
  • {isLoading ? <Spinner /> : <List />} — выбор из двух вариантов;
  • ранний return null или переменная с элементом до return — когда вариантов больше.

То же случается с items.length && … для пустого массива.

Уточняющие вопросы

Чем скрытие через условие отличается от CSS display: none?

Условие удаляет компонент вместе с его состоянием, а CSS оставляет его в дереве и сохраняет состояние.

Что вернуть из компонента, если показывать нечего?

null: компонент ничего не выведет.

Что прозвучало в вашем ответе
  • Объяснено, что && возвращает левый операнд.
  • Сказано, что React выводит 0, но не false и null.
  • Предложено логическое условие или тернарный оператор.

Спецификация: React: Conditional Rendering

React · Middle и выше

Когда useMemo и useCallback действительно помогают, а когда только усложняют код? Почему useCallback без memo у дочернего компонента обычно бесполезен?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Гарантирует ли useMemo, что вычисление не повторится?
  • Как проверить, что мемоизация помогла?

Разбор

Разумные случаи:

  • дорогое вычисление при рендере — сортировка или фильтрация тысяч элементов, которое видно в профайлере;
  • значение или функция уходит в компонент, обёрнутый в memo: новая ссылка на каждом рендере отменила бы пропуск рендера;
  • объект или функция — зависимость useEffect: без стабильной ссылки эффект перезапускался бы каждый раз.

Цена: код сложнее, зависимости нужно поддерживать, а кеш занимает память. Обернуть всё подряд — не оптимизация.

Часто лучше изменить структуру: опустить состояние ниже, передать тяжёлую часть через children, вынести объект за пределы компонента. React Compiler умеет мемоизировать автоматически, и в проектах с ним ручные useMemo нужны реже.

Уточняющие вопросы

Гарантирует ли useMemo, что вычисление не повторится?

Нет, это оптимизация производительности, а не гарантия: React может сбросить кеш. Код должен работать правильно и без него.

Как проверить, что мемоизация помогла?

Сравнить время рендера в React Profiler до и после на одном сценарии, желательно с замедлением процессора.

Что прозвучало в вашем ответе
  • Различены кеширование значения и функции.
  • Названы случаи пользы: дорогое вычисление, memo, зависимости эффекта.
  • Объяснено, почему без memo у ребёнка нет выигрыша.
  • Упомянуто измерение профайлером.

Спецификация: React: useMemo

React · Middle и выше

Страница настроек и редактор отчётов загружаются вместе со всем приложением. Как подгружать их только при переходе, что показывать во время загрузки и что делать, если файл не загрузился?

                        const ReportEditor = lazy(() => import('./ReportEditor'));

function ReportsPage() {
  return (
    <ErrorBoundary fallback={<LoadError />}>
      <Suspense fallback={<Spinner />}>
        <ReportEditor />
      </Suspense>
    </ErrorBoundary>
  );
}
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему при переключении вкладок уже показанный интерфейс вдруг заменяется спиннером?
  • Что ещё умеет Suspense, кроме ленивого кода?

Разбор

lazy принимает функцию с динамическим import(); модуль должен экспортировать компонент по умолчанию. Объявлять lazy нужно на верхнем уровне модуля: внутри компонента он создавался бы заново на каждом рендере и сбрасывал состояние.

Suspense показывает fallback, пока какой-то компонент внутри не готов. Где поставить границу — решение об интерфейсе: одна граница на страницу проще, несколько позволяют показывать готовые части раньше.

Если после выкладки новой версии старый файл удалён, импорт упадёт — его перехватывает Error Boundary. Чтобы переход не ждал загрузки, код можно начать загружать заранее, например при наведении на ссылку.

Уточняющие вопросы

Почему при переключении вкладок уже показанный интерфейс вдруг заменяется спиннером?

Новый ленивый компонент приостановил рендер, и ближайшая граница показала fallback. Обновление можно пометить через startTransition — тогда React оставит прежний интерфейс до готовности.

Что ещё умеет Suspense, кроме ленивого кода?

Ждать данные в фреймворках и библиотеках, которые его поддерживают, — например, в серверных компонентах или use с промисом.

Что прозвучало в вашем ответе
  • Названы lazy и динамический import().
  • Объяснена роль Suspense и fallback.
  • Ошибка загрузки обрабатывается Error Boundary.

Спецификация: React: lazy

React · Middle и выше

Поле поиска фильтрует большой список прямо при вводе, и буквы появляются с задержкой. Чем помогут useTransition и useDeferredValue и чем они отличаются от debounce?

                        const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
// <input value={query} onChange={(e) => setQuery(e.target.value)} />
// <List query={deferredQuery} dim={isStale} />
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Ускоряет ли переход сам рендер списка?
  • Для чего isPending?

Разбор

В обоих случаях React сначала рендерит срочное обновление — поле с новым текстом, а затем в фоне готовит тяжёлое. Если пришёл новый ввод, незаконченный фоновый рендер выбрасывается.

  • useTransition возвращает [isPending, startTransition]; внутри startTransition ставят несрочный setState. Подходит, когда вы управляете местом обновления.
  • useDeferredValue(value) — когда значение приходит снаружи, например через props: компонент получает отстающую версию.

Чтобы это работало, тяжёлый список обычно оборачивают в memo, иначе он рендерится и при срочном обновлении. Управляемое поле нельзя обновлять внутри перехода — ввод станет отставать.

Debounce уменьшает число запросов к серверу, а переходы — нет. Для сетевого поиска их часто сочетают.

Уточняющие вопросы

Ускоряет ли переход сам рендер списка?

Нет: работа та же, но она не блокирует ввод. Если рендер очень тяжёлый, нужна виртуализация или меньший объём.

Для чего isPending?

Показать, что результат обновляется: например, приглушить старый список, пока готовится новый.

Что прозвучало в вашем ответе
  • Разделены срочное и несрочное обновления.
  • Сказано, что фоновый рендер прерывается новым вводом.
  • Различены useTransition и useDeferredValue.
  • Объяснено отличие от debounce.

Спецификация: React: useTransition

React · Middle и выше

Всплывающая подсказка внутри карточки с overflow: hidden обрезается. Как помогает createPortal и как ведут себя события и контекст у компонента в портале?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Куда портал выводит разметку при серверном рендеринге?
  • Почему обработчик на document, закрывающий меню «по клику снаружи», может сломаться с порталом?

Разбор

Портал меняет только место в DOM. Поэтому:

  • подсказку или окно больше не обрезает overflow карточки и не ограничивает её z-index;
  • Context продолжает работать — портал видит провайдеры своих React-предков;
  • события всплывают по дереву React: клик внутри портала дойдёт до onClick карточки, хотя в DOM её там нет. Иногда это неожиданно, и всплытие приходится останавливать.

Доступность остаётся задачей разработчика: перенести фокус, вернуть его после закрытия, закрыть по Escape, а для модального окна сделать фон неактивным. Для модального окна нативный <dialog> с showModal() выводится в верхний слой браузера и часто избавляет от портала.

Уточняющие вопросы

Куда портал выводит разметку при серверном рендеринге?

На сервере нет document, поэтому порталы рендерят на клиенте — например, после монтирования.

Почему обработчик на document, закрывающий меню «по клику снаружи», может сломаться с порталом?

Проверка contains по DOM считает клик в портале внешним: элемента нет внутри DOM-узла меню. Нужно проверять и узел портала.

Что прозвучало в вашем ответе
  • Портал выводит разметку в другой DOM-узел.
  • Контекст сохраняется.
  • События всплывают по дереву React.
  • Упомянуты фокус и доступность или <dialog>.

Спецификация: React: createPortal

React · Middle и выше

В проекте на Next.js с App Router одни компоненты помечены 'use client', другие нет. Чем серверные компоненты отличаются от клиентских и что можно передавать между ними?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Это то же самое, что SSR?
  • Как не дать серверному модулю с секретами случайно попасть в клиентский код?

Разбор

Серверные компоненты (React Server Components) рендерятся на сервере или при сборке, и клиент получает результат, а не их JavaScript. Их сильные стороны — прямой доступ к данным и меньший bundle.

Клиентские компоненты нужны для интерактивности: useState, эффекты, обработчики, браузерные API. Они тоже рендерятся на сервере в HTML, а затем гидратируются.

Правила на границе:

  • props должны быть сериализуемыми: строки, числа, обычные объекты, массивы, даты, промисы; функции — только серверные действия ('use server');
  • клиентский компонент не может импортировать серверный, но может получить его как children;
  • 'use client' ставят как можно ниже по дереву, чтобы не тянуть в браузер лишнее.

Секреты нельзя передавать в props клиентского компонента: они окажутся в данных страницы.

Уточняющие вопросы

Это то же самое, что SSR?

Нет. SSR превращает в HTML и клиентские компоненты, а потом их код всё равно загружается. Серверные компоненты своего кода в браузер не отправляют вовсе.

Как не дать серверному модулю с секретами случайно попасть в клиентский код?

Импортировать в нём пакет server-only: сборка упадёт, если модуль окажется в клиентском bundle.

Что прозвучало в вашем ответе
  • Серверный компонент не отправляет свой код в браузер и не имеет состояния.
  • Объяснена роль 'use client' как границы.
  • Названы ограничения props: сериализуемость, children.

Спецификация: React: Server Components

Управление состоянием · Junior

В приложении есть открытый tooltip, выбранная валюта и содержимое корзины. Нужно ли сразу перенести всё в общий store?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Когда локальное состояние стоит поднять выше?
  • Сохранит ли Redux или Zustand данные после перезагрузки автоматически?

Разбор

Для каждого значения выясните, кто его читает и изменяет, должно ли оно пережить смену страницы и есть ли у него серверный владелец. Если состояние нужно двум соседним компонентам, часто достаточно общего родителя. Общий store полезен при действительно распределённом доступе и сложных переходах.

Отдельно определите хранение: общий store в памяти сам по себе не сохраняет данные после перезагрузки. Серверная корзина требует синхронизации, а её отображаемая копия не должна становиться независимым источником истины.

Уточняющие вопросы

Когда локальное состояние стоит поднять выше?

Когда несколько частей должны согласованно читать или менять одно значение; выбирают ближайшего подходящего владельца.

Сохранит ли Redux или Zustand данные после перезагрузки автоматически?

Нет, для этого нужна отдельная политика persistence или загрузка с сервера.

Что прозвучало в вашем ответе
  • Решение основано на потребителях и времени жизни.
  • Локальные значения не перенесены в общий store без причины.
  • Общее состояние отличено от постоянного хранения.

Спецификация: React: совместное состояние

Управление состоянием · Junior

Тема оформления нужна во многих ветках React. Когда достаточно Context и почему он сам по себе не заменяет все возможности стейт-менеджера?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Context обязательно должен содержать state?
  • Почему один контекст «всё приложение» может быть неудобен?

Разбор

Для темы можно хранить состояние в provider и передавать значение с действием переключения. Когда value меняется, использующие этот Context потребители получают обновление. Новый объект в value при каждом рендере может вызывать лишние обновления.

Разделяйте контексты с разной частотой изменения и областью использования. memo не блокирует обновление используемого контекста. Если нужна подписка на небольшие части большого часто меняющегося состояния, сравните возможности специализированного store.

Уточняющие вопросы

Context обязательно должен содержать state?

Нет: он может передавать неизменяемую конфигурацию или ссылку на сервис. Context отвечает за доступ к значению.

Почему один контекст «всё приложение» может быть неудобен?

Изменение одной части значения затронет многих потребителей и размоет границы ответственности.

Что прозвучало в вашем ответе
  • Context описан как передача значения по дереву.
  • Объяснены обновления потребителей при смене значения.
  • Названы задачи, которые Context сам не решает.

Спецификация: React: useContext

Управление состоянием · Junior

У формы много флагов: загрузка, ошибка, успех. Можно получить одновременно успех и ошибку. Как помогут явные состояния и reducer?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Где запускать запрос сохранения?
  • Что делать с повторным нажатием во время сохранения?

Разбор

Например, форма бывает idle, saving, success или error. Действие начала сохранения убирает предыдущую ошибку, успешный ответ содержит результат, отказ — причину. Переходы можно проверить отдельно от React.

Reducer должен быть чистым: без запросов, таймеров и изменения внешних объектов. useReducer не делает состояние глобальным: оно остаётся у вызвавшего компонента, пока вы явно не передадите его другим. Для гонок нескольких запросов может потребоваться идентификатор актуальной операции.

Уточняющие вопросы

Где запускать запрос сохранения?

В обработчике действия или другом выделенном слое побочных эффектов; затем передать reducer событие результата.

Что делать с повторным нажатием во время сохранения?

Определить контракт: игнорировать, отменить предыдущую операцию или поставить следующую. Это правило перехода, а не случайный набор флагов.

Что прозвучало в вашем ответе
  • Описаны допустимые состояния и события.
  • Reducer вычисляет переход без побочных эффектов.
  • Определено поведение повторного запуска.

Спецификация: React: выделение логики в reducer

Управление состоянием · Junior

Store хранит товары корзины, их число и общую стоимость. После удаления товара стоимость иногда остаётся старой. Как уменьшить риск рассогласования?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Нужно ли мемоизировать чтение одного поля?
  • Почему селектор не должен делать sort исходного массива?

Разбор

Количество и клиентскую предварительную сумму получают из актуальных позиций. Селектор — функция чтения и преобразования состояния; он не должен менять store. Для дорогих расчётов может понадобиться мемоизация, но сначала оценивают стоимость.

Подтверждённая сервером сумма заказа — другой факт: её нельзя подменять клиентским расчётом, если сервер применяет скидки, доставку или налоги. В интерфейсе различают предварительную и подтверждённую стоимость.

Уточняющие вопросы

Нужно ли мемоизировать чтение одного поля?

Обычно нет: простое чтение дешёвое. Мемоизация полезна для дорогого вычисления или стабильности результата при конкретной модели подписки.

Почему селектор не должен делать sort исходного массива?

Обычный sort меняет store при чтении. Сортируют копию или используют toSorted.

Что прозвучало в вашем ответе
  • Производные значения не дублируются без причины.
  • Селектор остаётся чистой функцией.
  • Предварительный расчёт отличён от подтверждённого сервером.

Спецификация: Redux: производные данные и селекторы

Управление состоянием · Junior

Список товаров с сервера нужен на нескольких экранах. Чем кеш TanStack Query или RTK Query отличается от ручного массива в общем store?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Где хранить открытую вкладку или текст несохранённого поля?
  • Почему не копировать каждый ответ Query ещё и в Redux-срез?

Разбор

Сервер остаётся владельцем данных; клиент хранит временную копию. Ключ запроса должен включать параметры, влияющие на результат, например категорию и страницу. Иначе разные экраны могут ошибочно разделить одну запись кеша.

Кеш не означает «никогда не запрашивать снова». Нужно понимать свежесть, время удаления неиспользуемых данных и условия повторной загрузки. В TanStack Query staleTime отвечает за свежесть, gcTime — за хранение неактивной записи; это разные настройки.

Уточняющие вопросы

Где хранить открытую вкладку или текст несохранённого поля?

Обычно в локальном или клиентском состоянии: это не ответ сервера и не обязательно задача кеша запросов.

Почему не копировать каждый ответ Query ещё и в Redux-срез?

Появятся два хранилища одного факта с отдельными правилами обновления. Если копия нужна как черновик, её жизненный цикл задают явно.

Что прозвучало в вашем ответе
  • Различены серверные данные и состояние интерфейса.
  • Параметры результата включены в ключ запроса.
  • Свежесть отличена от удаления записи кеша.

Спецификация: TanStack Query: настройки кеша

Управление состоянием · Middle и выше

В reducer обычного Redux нельзя менять массив, а в createSlice пишут state.items.push(item). Почему это не противоречие и где заканчивается разрешение на мутацию?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему state = newState внутри reducer не заменяет состояние?
  • Можно ли сохранить draft и изменить его позже в таймере?

Разбор

Можно изменить draft либо вернуть новое состояние. Не стоит одновременно менять draft и возвращать другой результат. Стрелка state => state.items.push(item) неявно возвращает длину массива; используйте тело в фигурных скобках без возврата.

Reducer остаётся чистым: запросы и таймеры выполняются вне него. Изменяемый синтаксис — удобство подготовки следующего состояния, а не побочный эффект над предыдущим. Обычный Redux reducer без Immer требует самостоятельного копирования изменяемых веток.

Уточняющие вопросы

Почему state = newState внутри reducer не заменяет состояние?

Это переназначение локального параметра. Для полной замены нужно return newState.

Можно ли сохранить draft и изменить его позже в таймере?

Нет: draft относится к текущему вызову подготовки состояния и не должен выходить за его пределы.

Что прозвучало в вашем ответе
  • Объяснён draft Immer.
  • Различены изменение draft и возврат нового состояния.
  • Мутации снаружи reducer и побочные эффекты не разрешены.

Спецификация: Redux Toolkit: reducers с Immer

Управление состоянием · Middle и выше

Компонент Zustand читает весь store и обновляется при каждом изменении чужого поля. Как сузить подписку и правильно обновлять вложенный объект?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему селектор state => ({ count: state.count }) требует внимания?
  • Когда нужно функциональное обновление set?

Разбор

Вместо useStore() используйте, например, useStore(state => state.cartCount). Если селектор возвращает новый объект или массив, его ссылочная стабильность требует внимания; для набора полей может подойти useShallow или отдельные селекторы.

Для вложенного имени: set(state => ({ user: { ...state.user, name } })). Поверхностное слияние не сохраняет вложенные поля само. Immer возможен через явное подключение, но не является поведением любого set по умолчанию.

Уточняющие вопросы

Почему селектор state => ({ count: state.count }) требует внимания?

Он создаёт новую ссылку при вызове. Это может вызвать лишние обновления и проблемы стабильности snapshot; результат нужно стабилизировать подходящим способом.

Когда нужно функциональное обновление set?

Когда новое значение вычисляется из предыдущего: функция получает актуальное состояние для этого обновления.

Что прозвучало в вашем ответе
  • Подписка ограничена нужным срезом.
  • Учтена стабильность ссылок результата селектора.
  • Вложенная структура обновляется с явным копированием.

Спецификация: Zustand: селекторы и обновления

Управление состоянием · Middle и выше

Поле в MobX-store меняется, но React-компонент не обновляется. Как связаны observable, observer и чтение значения при рендере?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Подпишется ли компонент на все поля переданного store?
  • Зачем computed вместо отдельного поля с итогом?

Разбор

Проверьте, что поле действительно наблюдаемое и компонент обёрнут в observer. Передавать ссылку на объект и читать поле внутри наблюдаемого компонента обычно полезнее, чем заранее вычитать примитив вне отслеживаемого рендера.

Вычисляемые значения описывают как computed, изменения группируют в action. После await продолжение уже не принадлежит синхронному действию: при соответствующей политике изменений используют runInAction или flow. Нельзя механически переносить модель неизменяемых reducer на реактивную модель MobX.

Уточняющие вопросы

Подпишется ли компонент на все поля переданного store?

Нет: важны observable-значения, прочитанные при отслеживаемом рендере.

Зачем computed вместо отдельного поля с итогом?

Он выражает производное значение и отслеживает его зависимости, уменьшая ручную синхронизацию.

Что прозвучало в вашем ответе
  • Проверены наблюдаемость данных и observer.
  • Названо чтение значения в отслеживаемом контексте.
  • Для асинхронных изменений учтены границы action.

Спецификация: MobX: интеграция с React

Управление состоянием · Middle и выше

После нажатия «Нравится» счётчик меняется сразу, а сервер иногда отклоняет запрос. Как организовать оптимистичное обновление и что делать с несколькими быстрыми нажатиями?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем отменять текущую загрузку перед временным изменением кеша?
  • Достаточно ли всегда вернуть сохранённый снимок при ошибке?

Разбор

В кеше запросов обычно отменяют конфликтующую загрузку, сохраняют исходное значение, применяют временное изменение, а после завершения инвалидируют или согласуют запись. Интерфейс явно сообщает об отказе.

Простой откат целого снимка опасен при нескольких незавершённых мутациях: ошибка первой может стереть успешную вторую. Можно сериализовать операции по сущности, хранить журнал изменений или использовать версии. Выбор зависит от операции; для критичного действия оптимистичное подтверждение может быть неуместно.

Уточняющие вопросы

Зачем отменять текущую загрузку перед временным изменением кеша?

Её старый ответ может затереть оптимистичный результат. Нужно согласовать конкурирующие обновления.

Достаточно ли всегда вернуть сохранённый снимок при ошибке?

Только если за это время не возникло других изменений. Иначе нужен более точный откат или получение актуального состояния.

Что прозвучало в вашем ответе
  • Есть временное обновление и обработка отказа.
  • Результат согласуется с сервером.
  • Учтена гонка нескольких операций и риск отката старого снимка.

Спецификация: TanStack Query: optimistic updates

Управление состоянием · Middle и выше

Store сохраняют целиком в localStorage. После обновления приложения часть пользователей видит сломанный интерфейс, а после выхода — данные прежнего аккаунта. Как спроектировать сохранение?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что делать с неизвестной версией записи?
  • Почему нельзя сохранять isLoading: true как обычную настройку?

Разбор

Флаги загрузки, ошибки, функции и экземпляры соединений обычно не являются сохраняемыми данными. Настройки пользователя и черновик требуют отдельного контракта. При смене аккаунта старые записи и кеши очищают или отделяют по владельцу.

Для SSR сервер не может прочитать браузерное хранилище: первый клиентский рендер должен согласоваться с серверным, а восстановление проводится по выбранной стратегии. Недоступность storage не должна ломать базовый интерфейс. Persistence не заменяет серверную проверку прав или безопасное хранение секретов.

Уточняющие вопросы

Что делать с неизвестной версией записи?

Применить поддерживаемую миграцию либо безопасно отклонить запись и сообщить о невозможности восстановить важный черновик.

Почему нельзя сохранять isLoading: true как обычную настройку?

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

Что прозвучало в вашем ответе
  • Выбран ограниченный сериализуемый набор данных.
  • Есть версия, проверка и миграция или сброс.
  • Учтены смена аккаунта и восстановление при SSR.

Спецификация: Redux: сериализуемое состояние

Сборка проектов · Junior

Проект содержит TypeScript, CSS и импорты картинок. Что происходит между запуском команды сборки и появлением файлов, которые можно отдать браузеру?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем точка входа отличается от выходного файла?
  • Зачем сборщику граф зависимостей?

Разбор

Разрешение импорта определяет, какой файл соответствует имени модуля. Преобразование убирает типы, обрабатывает JSX или CSS согласно конфигурации. По графу зависимостей сборщик решает, какие модули войдут в выходные пакеты и какие ресурсы нужно скопировать.

Проверка типов, линтер и тесты могут быть отдельными шагами: успешная упаковка не гарантирует их прохождение. Результат проверяют как готовое приложение, включая пути к ресурсам и режим сервера.

Уточняющие вопросы

Чем точка входа отличается от выходного файла?

Точка входа запускает обход зависимостей; выходных файлов может быть несколько, включая общие и отложенные chunks.

Зачем сборщику граф зависимостей?

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

Что прозвучало в вашем ответе
  • Описаны точки входа и разрешение импортов.
  • Названы преобразование, оптимизация и выходные ресурсы.
  • Сборка отделена от проверки типов и тестов.

Спецификация: webpack: основные понятия

Сборка проектов · Junior

На ноутбуке проект собирается, а в CI после установки зависимостей падает. Как помогают lockfile и npm ci и что ещё нужно зафиксировать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему диапазон версии в package.json недостаточен?
  • Можно ли установить только production-зависимости перед frontend-сборкой?

Разбор

В отличие от обычной установки, npm ci требует lockfile и завершится ошибкой, если он расходится с package.json. Существующая папка node_modules перед установкой удаляется. Обновление зависимости и lockfile проверяют вместе.

Одинаковое дерево не гарантирует побитово одинаковую сборку: влияют ОС, нативные зависимости, переменные окружения, время и скачивание внешних данных. Сравните логи и окружение, а не удаляйте lockfile как универсальный способ исправления.

Уточняющие вопросы

Почему диапазон версии в package.json недостаточен?

При новом разрешении диапазона может установиться другая подходящая версия и другое дерево транзитивных зависимостей.

Можно ли установить только production-зависимости перед frontend-сборкой?

Не всегда: сборщик и компилятор часто находятся в devDependencies и нужны на этапе сборки, даже если не нужны для отдачи готовых файлов.

Что прозвучало в вашем ответе
  • Объяснены назначение lockfile и поведение npm ci.
  • Проверены версии инструментов и параметры установки.
  • Установка зависимостей отделена от полной воспроизводимости артефакта.

Спецификация: npm: ci

Сборка проектов · Junior

Production-сборка TypeScript прошла, хотя в коде строка передаётся вместо числа. Почему преобразование TypeScript не всегда ловит ошибки типов?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Заменяет ли линтер проверку типов?
  • Почему нельзя считать dev-сервер проверкой production-сборки?

Разбор

Быстрое преобразование отдельного файла может выдать JavaScript, не анализируя совместимость типов между модулями. В Vite проверку типов рекомендуют запускать отдельно от преобразования.

В CI шаги установки, проверки типов, тестирования и сборки должны иметь явные команды и коды завершения. Для фреймворков с особыми файлами выбирают соответствующий checker. Типы всё равно не проверяют структуру входящего JSON во время работы.

Уточняющие вопросы

Заменяет ли линтер проверку типов?

Нет: назначение зависит от правил и конфигурации, обычный линт не является полной проверкой типов проекта.

Почему нельзя считать dev-сервер проверкой production-сборки?

Он использует другой путь обработки и может обслуживать лишь запрошенные модули; часть ошибок обнаружится только в отдельной проверке или сборке.

Что прозвучало в вашем ответе
  • Преобразование отделено от семантической проверки.
  • Предложен явный шаг typecheck.
  • Отмечено отсутствие runtime-проверки внешних данных.

Спецификация: Vite: TypeScript

Сборка проектов · Junior

После настройки старого target код запускается в нужном браузере, но падает на Array.prototype.toSorted. Почему target не гарантирует наличие всех API?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Добавит ли смена lib реализацию метода в JavaScript?
  • Почему наличие метода недостаточно проверять только на своём компьютере?

Разбор

Преобразовать синтаксис стрелочной функции и добавить отсутствующий метод массива — разные операции. Даже типы lib в TypeScript лишь описывают доступный API для компилятора, а не устанавливают его в браузер.

Определите поддерживаемые окружения и проверьте конкретные возможности. Полифил должен загрузиться до использования и соответствовать требуемой семантике; некоторые браузерные возможности полноценно воспроизвести нельзя. Не добавляйте все полифилы без оценки их размера.

Уточняющие вопросы

Добавит ли смена lib реализацию метода в JavaScript?

Нет, она меняет доступные объявления типов, а не поведение среды.

Почему наличие метода недостаточно проверять только на своём компьютере?

Пользователь может работать в другой версии браузера или WebView; нужна проверка целевой матрицы окружений.

Что прозвучало в вашем ответе
  • Различены синтаксис и API среды.
  • Типовые объявления не названы полифилом.
  • Предложена проверка целевых браузеров и осмысленный fallback.

Спецификация: Vite: совместимость production-сборки

Сборка проектов · Junior

Ключ внешнего сервиса записали в VITE_API_KEY и считают секретным, потому что файл .env не попадает в Git. Что окажется в клиентской сборке?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему условие по строке "false" может неожиданно включить функцию?
  • Можно ли спрятать секрет минификацией?

Разбор

В Vite переменные с префиксом VITE_ предназначены для клиентского доступа. Они обычно читаются как строки: значение "false" не является логическим false. Адрес публичного API может быть конфигурацией клиента, приватный ключ сервиса — нет.

Сборочное значение фиксируется при создании артефакта. Изменение окружения сервера, который только раздаёт готовые файлы, не перепишет этот JavaScript. Для одного артефакта на несколько окружений можно спроектировать загрузку публичной runtime-конфигурации.

Уточняющие вопросы

Почему условие по строке "false" может неожиданно включить функцию?

Непустая строка истинна при логическом приведении; значение нужно явно разобрать по контракту.

Можно ли спрятать секрет минификацией?

Нет: браузер должен получить данные для выполнения кода, а минификация не является шифрованием или контролем доступа.

Что прозвучало в вашем ответе
  • Клиентские переменные признаны публичными.
  • Build-time конфигурация отделена от runtime.
  • Учтён строковый тип значений окружения.

Спецификация: Vite: переменные окружения

Сборка проектов · Junior

Локально сайт работает из корня, а после публикации в /docs/ теряет стили и картинки. Что проверить в сборке и путях ресурсов?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему открыть готовый HTML через файловый протокол недостаточно?
  • Как отличить неверный путь скрипта от ошибки самого скрипта?

Разбор

Настройка base помогает сборщику переписать известные ему ссылки. Произвольная строка в коде, например URL из внешних данных, не обязана участвовать в таком преобразовании. Для обрабатываемого ресурса используйте поддерживаемый импорт или другой документированный механизм.

Проверьте готовую сборку именно по будущему префиксу, включая прямое открытие вложенного маршрута. Корректные пути ресурсов и настройка сервера для маршрутов SPA — две отдельные задачи. Убедитесь, что вместо отсутствующего JS сервер не возвращает HTML с кодом 200.

Уточняющие вопросы

Почему открыть готовый HTML через файловый протокол недостаточно?

Модульная загрузка, origin и маршрутизация отличаются от HTTP-размещения; нужен сервер предпросмотра.

Как отличить неверный путь скрипта от ошибки самого скрипта?

Проверить URL, статус, MIME-тип и фактическое тело ответа в Network.

Что прозвучало в вашем ответе
  • Проверены base и абсолютные пути.
  • Различены обработанные сборщиком ресурсы и произвольные URL.
  • Готовый сайт проверен под реальным префиксом.

Спецификация: Vite: обработка статических ресурсов

Сборка проектов · Middle и выше

После установки sideEffects: false bundle уменьшился, но исчезли стили и регистрация плагина. Как это связано с tree shaking?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Поможет ли именованный импорт, если библиотека выполняет большую инициализацию?
  • Почему проблема может не проявиться в dev?

Разбор

Неиспользуемый экспорт и эффект выполнения модуля — разные вещи. Файл может ничего не экспортировать, но регистрировать компонент или подключать стили. Такие файлы необходимо обозначить как имеющие побочные эффекты в соответствии с инструментом.

Статические ES-импорты облегчают анализ, но не гарантируют удаление всего лишнего. Проверьте production-артефакт, настройки оптимизации и реальные сценарии библиотеки. Не копируйте sideEffects: false как универсальный рецепт уменьшения размера.

Уточняющие вопросы

Поможет ли именованный импорт, если библиотека выполняет большую инициализацию?

Не обязательно: важные эффекты нельзя безопасно удалить только потому, что часть экспортов не используется.

Почему проблема может не проявиться в dev?

Production-путь включает более агрессивное исключение кода; dev может исполнять исходные модули иначе.

Что прозвучало в вашем ответе
  • Неиспользуемые экспорты отличены от эффектов импорта.
  • Поле sideEffects описано как обещание сборщику.
  • Нужные CSS и регистрация сохранены и проверены в production.

Спецификация: webpack: tree shaking и side effects

Сборка проектов · Middle и выше

Одна библиотека поставляет ESM и CommonJS. В Node.js тесты проходят, а браузерная сборка выбирает другой файл и падает. Как понять, какой вариант пакета используется?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему нельзя просто импортировать любой файл из node_modules?
  • Где искать доказательство выбора файла?

Разбор

Сопоставьте package.json зависимости, активные условия resolver и итоговый граф сборки. Условные экспорты могут направить разные потребители к разным файлам. Глубокий импорт внутреннего файла может обходить публичный контракт и перестать работать после обновления.

ESM/CommonJS interop не всегда даёт одинаковую форму default- и именованных экспортов. Проверьте фактическое значение импорта в проблемной среде и используйте документированный вход пакета. Две независимо загруженные реализации также могут дублировать состояние библиотеки.

Уточняющие вопросы

Почему нельзя просто импортировать любой файл из node_modules?

Пакет может закрыть внутренние пути через exports; внутреннее устройство не обязано быть стабильным API.

Где искать доказательство выбора файла?

В отчёте resolver, графе модулей или source map итогового артефакта, а не только в названии зависимости.

Что прозвучало в вашем ответе
  • Проверены условные экспорты и целевое окружение.
  • Учтена форма ESM/CommonJS-импортов.
  • Использован публичный вход пакета.

Спецификация: Node.js: пакеты и условные экспорты

Сборка проектов · Middle и выше

В мониторинге ошибка указывает на одну строку минифицированного JS. Как source maps помогают найти исходник и что важно при их публикации?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем сохранять точный bundle вместе с картой?
  • Достаточно ли включить карты только локально?

Разбор

Сохраните идентификатор релиза и согласованный комплект файлов. Карта от похожей, но заново собранной версии может давать неверные строки. Само отсутствие комментария sourceMappingURL не делает публично размещённую карту закрытой.

Карты могут включать исходный текст через sourcesContent, поэтому политика доступа должна быть осознанной. Даже приватные карты не позволяют хранить секреты в клиентском коде. Проверьте расшифровку тестовой ошибки для опубликованного артефакта.

Уточняющие вопросы

Зачем сохранять точный bundle вместе с картой?

Смещения относятся к конкретному выходному файлу; другая минификация или зависимости изменят соответствие.

Достаточно ли включить карты только локально?

Нет: мониторингу нужен доступ к картам именно проблемной production-версии.

Что прозвучало в вашем ответе
  • Объяснено соответствие позиций артефакта исходнику.
  • Карта привязана к точному релизу.
  • Продуманы доступ и проверка расшифровки.

Спецификация: webpack: варианты source maps

Сборка проектов · Middle и выше

После правки компонента HMR сохраняет удобное состояние, но после полной перезагрузки появляется ошибка. Какие гарантии даёт горячее обновление?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему после нескольких обновлений обработчик вызывается много раз?
  • Нужно ли считать любой сброс состояния ошибкой HMR?

Разбор

Сначала воспроизведите проблему на чистом запуске, затем сравните с горячим обновлением. Старые обработчики и ресурсы должны освобождаться, если модуль создаёт их вне жизненного цикла компонента. Не каждая правка сохраняет состояние: это зависит от границ обновления и интеграции фреймворка.

Dev-сервер оптимизирует скорость разработки, а production — готовый артефакт. Различаются условия окружения, оптимизация и способ загрузки зависимостей. Проверка HMR не заменяет сборку и запуск её результата.

Уточняющие вопросы

Почему после нескольких обновлений обработчик вызывается много раз?

Новая версия модуля могла добавить подписку без удаления старой; нужна очистка при замене или правильный жизненный цикл.

Нужно ли считать любой сброс состояния ошибкой HMR?

Нет: некоторые изменения требуют полной перезагрузки или пересоздания компонента.

Что прозвучало в вашем ответе
  • Горячая замена отделена от холодного запуска.
  • Проверена очистка ресурсов модуля.
  • Production-артефакт проверяется самостоятельно.

Спецификация: Vite: HMR API

Сборка проектов · Middle и выше

В CI добавили кеш сборки по хешу исходников. После изменения переменной окружения он возвращает старый результат. Что должно входить в ключ кеша?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Зачем учитывать версию компилятора?
  • Нужно ли включать все переменные CI без разбора?

Разбор

Разделяйте кеш скачивания пакетов, промежуточных преобразований и итоговых задач: у них разные входы и цена ошибки. Для monorepo учитывают также зависимости задачи и результаты генерации.

Нельзя кешировать недетерминированный результат как чистую функцию без описания внешних входов. Пропуск кеша должен приводить к обычной корректной сборке. Сравните холодный и повторный запуск, затем измените каждый важный вход и убедитесь, что результат пересчитывается.

Уточняющие вопросы

Зачем учитывать версию компилятора?

Одинаковый исходник может преобразовываться по-разному разными версиями инструментов.

Нужно ли включать все переменные CI без разбора?

Нет: случайные ID запуска уничтожат пользу кеша. Выделяют именно влияющие на результат значения и отдельно защищают секреты.

Что прозвучало в вашем ответе
  • Перечислены значимые входы задачи.
  • Различены виды кеша.
  • Проверена инвалидация при изменении входов и корректность cache miss.

Спецификация: Turborepo: кеширование задач

Сборка проектов · Middle и выше

После выкладки пользователи с давно открытой вкладкой получают 404 при открытии ленивого раздела. Новая главная страница работает. Как связаны релизы и хешированные файлы?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему хеш в имени удобнее перезаписи app.js?
  • Почему принудительная перезагрузка не всегда приемлема?

Разбор

Сначала публикуют ресурсы нового релиза, затем переключают HTML или указатель версии. Старые хешированные файлы сохраняют на срок, согласованный с временем жизни клиентов и кешей. Файл с прежним хешем нельзя заменить другим содержимым.

Новый HTML должен ссылаться на целостный набор ресурсов. Предусмотрите обработку ошибки устаревшего клиента и безопасное обновление с сохранением черновика. Одна лишь очистка CDN не обновляет уже работающий JavaScript в открытой вкладке.

Уточняющие вопросы

Почему хеш в имени удобнее перезаписи app.js?

Новое содержимое получает новый URL, что позволяет долго кешировать неизменяемый ресурс и различать версии.

Почему принудительная перезагрузка не всегда приемлема?

Пользователь может потерять несохранённые данные; сначала нужна стратегия их сохранения или предупреждения.

Что прозвучало в вашем ответе
  • Объяснены ссылки старой вкладки на прежние chunks.
  • Публикация сохраняет согласованный набор файлов.
  • Предусмотрены хранение старых ресурсов и восстановление клиента.

Спецификация: Vite: ошибки загрузки при выкладке

Сборка проектов · Middle и выше

Production-очистка CSS удалила класс, который приложение собирает из строки по данным API. Почему анализатор его не увидел и как сохранить нужные стили без отключения оптимизации?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему проблема видна только в production?
  • Чем плох список сохранения «все классы»?

Разбор

Вместо произвольного построения имени "badge-" + status задайте карту разрешённых статусов на полные имена классов. При использовании анализатора проверьте, какие файлы и шаблоны он сканирует. Часть классов может поступать из библиотек или серверных шаблонов.

Не разрешайте любой класс из внешних данных без контракта. Для действительно динамических величин, например ширины прогресса, может подойти CSS custom property. После extraction важно также проверить порядок каскада и загрузку стилей отложенных частей.

Уточняющие вопросы

Почему проблема видна только в production?

Удаление неиспользуемых правил может быть включено только в production pipeline.

Чем плох список сохранения «все классы»?

Он практически отменяет оптимизацию и маскирует неверно заданные входы анализатора.

Что прозвучало в вашем ответе
  • Объяснено ограничение статического поиска классов.
  • Предложена явная карта или ограниченный safelist.
  • Проверены источники классов и готовые состояния страницы.

Спецификация: Tailwind CSS: обнаружение классов

Сборка проектов · Middle и выше

Пакет UI в monorepo импортирует библиотеку, которую сам не объявляет. В корневой установке всё работает, а при публикации ломается. Почему?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему недостаточно проверить приложение из того же monorepo?
  • Нужно ли превращать все зависимости в peer?

Разбор

Различайте runtime-зависимости, инструменты разработки и peerDependencies — ожидания к совместимой библиотеке окружения потребителя. Например, UI-библиотека может объявлять React как peer, чтобы согласовать общую среду, а локальную версию использовать для разработки.

Проверьте содержимое публикуемого пакета, его exports, собранные файлы и установку в отдельный пример. Workspace-ссылка на исходники не доказывает, что архив публикации содержит всё нужное. Дублирование экземпляров библиотек с общим контекстом тоже может менять поведение.

Уточняющие вопросы

Почему недостаточно проверить приложение из того же monorepo?

Оно может иметь зависимости и пути, которых не будет у внешнего потребителя.

Нужно ли превращать все зависимости в peer?

Нет: peer означает требование к окружению потребителя, а не универсальный способ уменьшения пакета.

Что прозвучало в вашем ответе
  • Пакет явно объявляет используемые зависимости.
  • Различены dependencies, devDependencies и peerDependencies.
  • Проверены публикационный артефакт и отдельный потребитель.

Спецификация: npm: поля package.json

Сборка проектов · Middle и выше

Сборка выросла с минуты до десяти после добавления плагинов и генерации страниц. Как определить, поможет ли смена bundler?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему нельзя сравнивать только тёплую локальную сборку с холодным CI?
  • Как понять, что ускорение не получено отключением нужной проверки?

Разбор

Измерьте холодный и повторный запуск на одинаковом окружении. Найдите тяжёлые плагины, чрезмерно широкое сканирование, повторную обработку файлов и последовательные внешние запросы. Профиль и длительность фаз полезнее общего впечатления «сборщик медленный».

Оптимизируйте подтверждённую причину: сузьте входы, устраните повторную работу, корректно кешируйте или ограниченно распараллельте независимые задачи. Проверьте, что итоговый артефакт и проверки не стали хуже. Увеличение параллельности может упереться в память или диск.

Уточняющие вопросы

Почему нельзя сравнивать только тёплую локальную сборку с холодным CI?

Различаются кеши, ресурсы машины и установка зависимостей; такое сравнение смешивает несколько причин.

Как понять, что ускорение не получено отключением нужной проверки?

Зафиксировать состав pipeline и сравнить выполненные этапы и свойства артефакта до и после.

Что прозвучало в вашем ответе
  • Время разделено по фазам.
  • Измерения учитывают кеш и окружение.
  • Оптимизация адресует подтверждённый bottleneck и сохраняет проверки.

Спецификация: Vite: производительность инструментов

Git и командная работа · Junior

Вы поправили стили шапки и заодно файл с настройками для локальной отладки. Как закоммитить только стили? Что такое индекс (staging area) и каким должен быть хороший коммит?

                        git status
git add src/header.css
git diff --staged
git commit -m "Выровнять меню в шапке на мобильных"
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем git commit -am опасен?
  • Как исправить сообщение последнего коммита, если он ещё не отправлен?

Разбор

У файла в Git три состояния: изменён в рабочей папке, подготовлен в индексе, сохранён в коммите. Индекс позволяет собрать коммит из части изменений.

  • git add путь — добавить файл; git add -p — выбрать отдельные фрагменты;
  • git diff показывает то, что ещё не в индексе, git diff --staged — то, что попадёт в коммит;
  • git restore --staged путь убирает файл из индекса, не трогая изменения.

Хороший коммит делает одно дело, проект после него собирается, а сообщение в повелительной форме объясняет, что и зачем изменено. Локальные настройки, которые никогда не должны попадать в репозиторий, вносят в .gitignore.

Уточняющие вопросы

Чем git commit -am опасен?

Он добавляет все изменённые отслеживаемые файлы, включая случайные правки. Новые файлы он при этом не добавляет.

Как исправить сообщение последнего коммита, если он ещё не отправлен?

git commit --amend. После отправки в общую ветку переписывать коммит нельзя без договорённости.

Что прозвучало в вашем ответе
  • Объяснена роль индекса.
  • Добавлен только нужный файл или фрагмент.
  • Изменения проверены перед коммитом.
  • Названы признаки хорошего коммита.

Спецификация: Pro Git: запись изменений в репозиторий

Git и командная работа · Junior

Нужно сделать новую форму обратной связи, пока коллега правит главную. Как работать в отдельной ветке и как потом влить её в main? Что такое fast-forward?

                        git switch main
git pull
git switch -c feature/feedback-form
# ...коммиты...
git push -u origin feature/feedback-form
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Чем git pull отличается от git fetch?
  • Что делать с веткой после слияния?

Разбор

Ветка в Git — подвижный указатель на коммит, поэтому создаётся мгновенно. Работа в своей ветке не мешает другим и позволяет показать изменения на ревью до попадания в main.

Слияние:

  • fast-forward — если main не ушла вперёд, Git просто передвигает её на последний коммит ветки, история остаётся линейной;
  • трёхстороннее слияние — если обе ветки получили коммиты, Git объединяет изменения относительно общего предка и создаёт коммит слияния с двумя родителями.

Чем короче живёт ветка, тем меньше конфликтов. Свежие изменения main периодически забирают в свою ветку.

Уточняющие вопросы

Чем git pull отличается от git fetch?

fetch только скачивает новые коммиты с сервера, pull после этого ещё сливает их в текущую ветку (или делает rebase, если так настроено).

Что делать с веткой после слияния?

Удалить локально и на сервере: git branch -d имя, git push origin --delete имя. Коммиты останутся в main.

Что прозвучало в вашем ответе
  • Ветка создана от актуального main.
  • Описан путь слияния: pull request или git merge.
  • Объяснён fast-forward и коммит слияния.

Спецификация: Pro Git: ветвление в Git

Git и командная работа · Junior

При слиянии Git сообщил о конфликте в header.css. Что это значит и как его разрешить?

                        <<<<<<< HEAD
.menu { gap: 16px; }
=======
.menu { gap: 24px; display: flex; }
>>>>>>> feature/menu
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Может ли слияние без конфликтов всё равно сломать код?
  • Как уменьшить число конфликтов?

Разбор

Между <<<<<<< HEAD и ======= — версия текущей ветки, ниже до >>>>>>> — версия сливаемой. Порядок действий:

  1. git status покажет файлы с конфликтами;
  2. в каждом решить, каким должен быть результат: выбрать вариант или объединить, и удалить маркеры;
  3. собрать проект и проверить, что работает;
  4. git add файл, затем git commit (или git rebase --continue при rebase).

Если запутались, git merge --abort вернёт состояние до слияния. Редакторы вроде VS Code показывают конфликт с кнопками выбора, но решение по смыслу принимает человек — при сомнении спрашивают автора другого изменения.

Уточняющие вопросы

Может ли слияние без конфликтов всё равно сломать код?

Да: изменения в разных строках могут быть несовместимы по смыслу — например, одна ветка переименовала функцию, а другая добавила её вызов. Поэтому после слияния запускают сборку и тесты.

Как уменьшить число конфликтов?

Небольшие короткоживущие ветки, частое обновление от main, единый форматтер кода в проекте.

Что прозвучало в вашем ответе
  • Объяснено, когда возникает конфликт.
  • Названы маркеры и их смысл.
  • Описаны шаги: правка, проверка, git add, завершение.
  • Назван способ отменить слияние.

Спецификация: Pro Git: основы конфликтов слияния

Git и командная работа · Junior

В общую ветку попал коммит, который сломал сборку. А локально у вас есть ещё не отправленный неудачный коммит. Как отменить каждый из них? Чем отличаются git revert, git reset и git restore?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как временно убрать незаконченные правки, чтобы переключиться на срочную задачу?
  • Что такое git reflog?

Разбор

  • git restore файл — вернуть файл к версии из последнего коммита, незакоммиченные правки пропадут;
  • git reset HEAD~1 — передвинуть ветку на коммит назад. --soft оставляет изменения подготовленными, по умолчанию (--mixed) — в рабочей папке, --hard стирает их;
  • git revert хеш — создать коммит, отменяющий указанный. История сохраняется, коллегам не нужно ничего переделывать.

Правило: историю, которую уже получили другие, не переписывают. Для общей ветки — revert, для своих локальных коммитов — reset или commit --amend.

Даже после reset --hard коммиты ещё какое-то время можно найти через git reflog.

Уточняющие вопросы

Как временно убрать незаконченные правки, чтобы переключиться на срочную задачу?

git stash сохраняет их в отдельный список, git stash pop возвращает. Либо закоммитить в рабочую ветку как черновик.

Что такое git reflog?

Журнал перемещений HEAD в вашем локальном репозитории. По нему находят «потерянные» коммиты после reset или неудачного rebase.

Что прозвучало в вашем ответе
  • Для отправленного коммита выбран revert.
  • Для локального — reset, различены режимы.
  • Объяснено назначение restore.
  • Сформулировано правило о чужой истории.

Спецификация: Pro Git: операции отмены

Git и командная работа · Junior

Вы открываете первый pull request в команде. Что в нём должно быть, чтобы его быстро проверили? Как реагировать на замечания ревьюера?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Что такое draft pull request?
  • Нужно ли сжимать коммиты перед слиянием?

Разбор

Что помогает ревью:

  • размер — сотни строк, а не тысячи; большую задачу дробят на несколько PR;
  • описание: цель, ключевые решения, как проверить вручную, что не вошло;
  • скриншоты или видео для изменений интерфейса;
  • без случайных файлов, отладочного кода и несвязанного форматирования;
  • пройденные линтер, тесты и сборка в CI.

Замечания — о коде, а не о человеке. На каждое отвечают: исправлено, или вот почему сделано так. Если спор затянулся, лучше обсудить голосом. Мелкие стилевые вопросы должны решать форматтер и линтер, а не ревьюер.

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

Уточняющие вопросы

Что такое draft pull request?

Черновик: показать работу заранее и получить ранний отзыв, не прося о полном ревью и не допуская слияния.

Нужно ли сжимать коммиты перед слиянием?

Зависит от правил команды. Часто используют squash merge: в main попадает один аккуратный коммит на PR.

Что прозвучало в вашем ответе
  • Названы размер и описание PR.
  • Упомянуты самопроверка и CI.
  • Описано конструктивное обсуждение замечаний.

Спецификация: Google: The CL author’s guide

Git и командная работа · Middle и выше

Ваша ветка отстала от main на десять коммитов. Обновить её через merge или через rebase? Чем различаются результаты и когда rebase опасен?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему после rebase git push отклоняется и чем --force-with-lease лучше --force?
  • Чем squash merge отличается от rebase?

Разбор

Merge честно отражает, как шла работа, ничего не переписывает, но в истории появляются коммиты слияния, и её сложнее читать.

Rebase по очереди применяет ваши коммиты к новой базе. Конфликты могут появиться на каждом коммите. Плюсы — линейная история, удобная для git log и git bisect. Минус — коммиты получают новые хеши: если кто-то основывал работу на старых, у него появятся дубли и конфликты.

Практика:

  • свою ветку обновляют через rebase и отправляют с --force-with-lease;
  • общие ветки (main, release) не переписывают;
  • интерактивный rebase -i позволяет перед ревью объединить и переименовать коммиты.

Выбор — в соглашении команды; важнее единообразие, чем сам вариант.

Уточняющие вопросы

Почему после rebase git push отклоняется и чем --force-with-lease лучше --force?

История на сервере больше не является предком вашей. --force-with-lease перезапишет ветку, только если на сервере никто не добавил коммитов с момента вашего последнего получения.

Чем squash merge отличается от rebase?

Squash собирает все изменения ветки в один новый коммит в main; отдельные коммиты ветки в истории не сохраняются.

Что прозвучало в вашем ответе
  • Объяснено, что rebase переписывает коммиты.
  • Сравнены виды истории.
  • Сформулировано, когда rebase опасен.
  • Упомянут безопасный force push.

Спецификация: Pro Git: перебазирование

Git и командная работа · Middle и выше

Вчера после релиза сломалась сортировка, а с прошлой рабочей версии в main пришло 200 коммитов. Как быстро найти коммит, который внёс ошибку?

                        git bisect start
git bisect bad HEAD
git bisect good v2.4.0
# проверить, затем git bisect good или git bisect bad
git bisect run npm test -- sort.test.js
git bisect reset
                    
Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как найти, кто и когда изменил конкретную строку?
  • Что делать, если на коммите проект не собирается?

Разбор

Каждая проверка вдвое сокращает диапазон, поэтому шагов — около log₂ числа коммитов. После нахождения Git показывает первый плохой коммит; git bisect reset возвращает исходную ветку.

git bisect run команда автоматизирует поиск: код возврата 0 — хорошо, от 1 до 127 (кроме 125) — плохо, 125 — пропустить коммит, который нельзя проверить.

Bisect работает хорошо, когда коммиты небольшие и каждый собирается. Огромные коммиты и сломанная сборка на промежуточных шагах сильно снижают пользу — ещё одна причина делать аккуратную историю.

Найденный коммит не всегда содержит саму ошибку: он мог лишь проявить старую. После нахождения пишут тест на этот случай.

Уточняющие вопросы

Как найти, кто и когда изменил конкретную строку?

git blame файл или поиск по истории изменений строки: git log -S "текст".

Что делать, если на коммите проект не собирается?

git bisect skip — Git выберет соседний коммит.

Что прозвучало в вашем ответе
  • Назван git bisect и двоичный поиск.
  • Описаны отметки good и bad.
  • Упомянута автоматизация через bisect run.

Спецификация: Git: git-bisect

Git и командная работа · Middle и выше

Коллега закоммитил и отправил файл .env с ключом платёжного API. Он предлагает просто удалить файл следующим коммитом. Достаточно ли этого и что нужно сделать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему .gitignore не помог бы, если файл уже отслеживается?
  • Является ли секретом ключ в переменной VITE_?

Разбор

Порядок действий:

  1. Отозвать ключ у поставщика сервиса и выдать новый: считаем, что старый уже скомпрометирован. Это единственный надёжный шаг — бот может найти ключ в публичном репозитории за минуты.
  2. Проверить журналы использования ключа на подозрительные операции.
  3. Удалить файл из репозитория (git rm --cached .env) и добавить в .gitignore; в репозитории оставить пример .env.example без значений.
  4. Если нужно убрать ключ из истории — git filter-repo и force push с уведомлением всей команды; кеши, форки и клоны всё равно могли его сохранить.

Профилактика: сканеры секретов (gitleaks, проверка на стороне хостинга репозиториев) в pre-commit и CI, хранение секретов в хранилище окружения CI и на сервере, а не в коде.

Уточняющие вопросы

Почему .gitignore не помог бы, если файл уже отслеживается?

.gitignore влияет только на неотслеживаемые файлы. Отслеживаемый нужно сначала убрать из индекса через git rm --cached.

Является ли секретом ключ в переменной VITE_?

Нет: такие переменные встраиваются в клиентский JavaScript. Секретные ключи используют только на сервере.

Что прозвучало в вашем ответе
  • Ключ отозван и перевыпущен в первую очередь.
  • Объяснено, что ключ остаётся в истории.
  • Названы .gitignore и очистка истории.
  • Предложена профилактика: сканер секретов.

Спецификация: GitHub: Removing sensitive data

Git и командная работа · Middle и выше

Команда спорит о процессе: долгие ветки фич с выпуском раз в месяц или частые небольшие слияния в main. Чем отличаются GitFlow и trunk-based development и как выкладывать незаконченную функцию?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Как в trunk-based исправить ошибку в уже выпущенной версии?
  • Чем опасны забытые флаги?

Разбор

GitFlow: develop для текущей работы, ветки release/* для подготовки выпуска, hotfix/* для срочных исправлений. Подходит для продуктов с версиями, например библиотек или мобильных приложений. Минусы — сложность и большие слияния.

Trunk-based: ветки живут часы или дни, обязательны автоматические проверки и ревью, выкладка частая. Конфликтов меньше, обратная связь быстрее.

Feature flags позволяют влить код, но не показывать функцию пользователям: включить её для команды, части аудитории или выключить без отката релиза. Флаги нужно удалять после запуска, иначе код зарастает ветвлениями.

Выбор зависит от частоты выпусков, зрелости тестов и CI, а не от моды.

Уточняющие вопросы

Как в trunk-based исправить ошибку в уже выпущенной версии?

Исправить в main и выложить новую версию; если нужна поддержка старой линии, перенести исправление в ветку выпуска через cherry-pick.

Чем опасны забытые флаги?

Растёт число комбинаций, которые никто не тестирует, а мёртвый код усложняет изменения.

Что прозвучало в вашем ответе
  • Описаны GitFlow и trunk-based.
  • Названы условия выбора.
  • Упомянуты feature flags и их цена.

Спецификация: Trunk Based Development

Производительность · Junior

Пользователь говорит «сайт тормозит»: главная открывается быстро, но фильтр каталога долго не реагирует. С чего начать измерение?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему быстрый первый экран не доказывает быструю работу фильтра?
  • Можно ли считать появление спиннера завершением задачи?

Разбор

Зафиксируйте устройство, объём каталога, состояние кеша и шаги воспроизведения. Измеряйте задержку от изменения фильтра до видимого результата и отдельно до первого визуального отклика. Запишите профиль этого действия.

Core Web Vitals описывают загрузку основного содержимого, отзывчивость и стабильность раскладки, но не заменяют метрику конкретной задачи. Ускорение должно улучшать сценарий без потери корректности и доступности, а не только менять число в отчёте.

Уточняющие вопросы

Почему быстрый первый экран не доказывает быструю работу фильтра?

После загрузки главный поток может быть занят обработкой данных и рендерингом; это другая фаза работы страницы.

Можно ли считать появление спиннера завершением задачи?

Нет: это обратная связь. Время до полезного результата измеряют отдельно.

Что прозвучало в вашем ответе
  • Определены действие и полезный результат.
  • Загрузка отличена от отзывчивости.
  • Зафиксированы данные и условия воспроизведения.

Спецификация: web.dev: Web Vitals

Производительность · Junior

Lighthouse на ноутбуке показывает хороший результат, а реальные пользователи жалуются на задержки. Почему лабораторные и полевые измерения расходятся?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему среднее время может скрыть проблему?
  • Нужно ли отказаться от лабораторных тестов?

Разбор

Один локальный запуск может не включать позднее открытие меню, большой аккаунт или сторонние скрипты после согласия пользователя. Полевые распределения помогают увидеть медленные устройства и редкие тяжёлые сценарии.

Лаборатория удобна для воспроизведения причины и проверки гипотезы. После изменения проверьте результат и в реальных данных с учётом времени распространения релиза. Отсутствие полевых измерений у страницы не означает отсутствие проблемы.

Уточняющие вопросы

Почему среднее время может скрыть проблему?

Быстрое большинство маскирует медленный хвост; полезны процентили и разбиение по устройствам, маршрутам и версиям.

Нужно ли отказаться от лабораторных тестов?

Нет: они дают контролируемые условия и быстрый сигнал регрессии, дополняя полевые измерения.

Что прозвучало в вашем ответе
  • Различены контролируемые и реальные условия.
  • Предложены сегменты и распределения вместо одного числа.
  • Лаборатория используется для диагностики, поле — для проверки пользовательского эффекта.

Спецификация: web.dev: лабораторные и полевые данные

Производительность · Junior

После изменения кода страница загрузилась на 100 мс быстрее в одном прогоне. Как проверить, что это результат оптимизации, а не случайность?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему два прогона с разным состоянием кеша несопоставимы?
  • Что делать, если разброс больше ожидаемого улучшения?

Разбор

Измеряйте до и после на одинаковом сценарии, избегая одновременных изменений нескольких факторов. Холодный запуск и повторное посещение проверяют отдельно. Расширения браузера, фоновые процессы и открытые инструменты могут влиять на результат.

Сохраняйте трассировки и численные результаты, чтобы связать изменение с конкретной фазой работы. Важно оценить заметность эффекта для пользователя и убедиться, что он не получен отключением функциональности.

Уточняющие вопросы

Почему два прогона с разным состоянием кеша несопоставимы?

Во втором часть ресурсов или вычислений уже готова, поэтому ускорение может не иметь отношения к правке.

Что делать, если разброс больше ожидаемого улучшения?

Уточнить условия и увеличить число повторов либо признать, что текущие измерения не позволяют уверенно подтвердить эффект.

Что прозвучало в вашем ответе
  • Условия сравнения зафиксированы.
  • Есть повторные измерения и оценка разброса.
  • Результат связан с фазой работы и пользовательским сценарием.

Спецификация: Chrome DevTools: запись производительности

Производительность · Junior

JavaScript хорошо сжат и быстро скачивается, но на слабом телефоне страница долго не отвечает. Какие затраты остаются после передачи файла?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Одинаково ли полезно уменьшить JS и картинку на одинаковое число байтов?
  • Показывает ли размер gzip объём работы главного потока?

Разбор

Сравните Network с записью главного потока. Если загрузка завершена, а CPU занят инициализацией, уменьшение сетевых байтов само по себе не устранит задержку. Найдите дорогие функции, объём начальных данных и ненужные стартовые действия.

Используйте production-сборку и реалистичное замедление CPU. Отложенный код полезен, если он действительно не нужен сразу; если сразу же запускать весь отложенный код, стоимость выполнения никуда не исчезнет.

Уточняющие вопросы

Одинаково ли полезно уменьшить JS и картинку на одинаковое число байтов?

Нет: ресурсы имеют разную стоимость обработки. JS дополнительно исполняется и может инициировать ещё работу.

Показывает ли размер gzip объём работы главного потока?

Нет: он описывает размер передачи при таком сжатии, а не время выполнения.

Что прозвучало в вашем ответе
  • Названы затраты разбора, компиляции и выполнения.
  • Сеть сопоставлена с профилем CPU.
  • Размер передачи не принят за единственную метрику.

Спецификация: web.dev: затраты на JavaScript

Производительность · Junior

Миниатюра 120×120 показывает исходное фото 6000×4000. Файл сжат неплохо, но галерея потребляет много памяти. Почему уменьшения размера через CSS недостаточно?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Нужно ли всегда выбирать самое маленькое разрешение?
  • Почему нельзя оценивать память только по килобайтам JPEG?

Разбор

Проверьте выбранный браузером ресурс, его пиксельные размеры, переданные байты и поведение галереи при прокрутке. srcset и sizes помогают выбрать подходящий файл; формат сжатия и разрешение решают разные части задачи.

Декодированные изображения могут занимать намного больше памяти, чем файл на диске, а точный расход зависит от браузера и формата. Не делайте все миниатюры полноразмерными только ради возможности будущего увеличения: большой вариант можно получать при открытии просмотра.

Уточняющие вопросы

Нужно ли всегда выбирать самое маленькое разрешение?

Нет: изображение должно оставаться достаточно чётким с учётом размера и плотности экрана. Нужен подходящий вариант.

Почему нельзя оценивать память только по килобайтам JPEG?

JPEG хранит сжатые данные; после декодирования и подготовки к отображению представление другое.

Что прозвучало в вашем ответе
  • Различены CSS-размер, размер файла и пиксельное разрешение.
  • Предложены варианты ресурса под размер отображения.
  • Учтены декодирование и память, а не только сеть.

Спецификация: web.dev: изображения подходящего размера

Производительность · Junior

После подключения чата поддержки и аналитики ухудшилась отзывчивость. Как проверить вклад сторонних скриптов, если основной код приложения не менялся?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему загрузка после load не гарантирует хороший INP?
  • Достаточно ли смотреть только размер собственного bundle?

Разбор

Изучите инициаторы запросов, объём зависимостей и выполняемые задачи. Проверьте, не подключён ли один сервис дважды и не запускается ли тяжёлая инициализация на каждом переходе. Временное блокирование домена в диагностике помогает проверить гипотезу.

Решение может включать загрузку по открытию виджета, более лёгкую интеграцию или согласованное удаление ненужного сервиса. Не отключайте важную функцию только ради балла без проверки пользовательских и продуктовых требований.

Уточняющие вопросы

Почему загрузка после load не гарантирует хороший INP?

Пользователь может взаимодействовать со страницей в момент позднего выполнения стороннего кода.

Достаточно ли смотреть только размер собственного bundle?

Нет: сторонний код может загружаться отдельно и создавать существенную сеть и CPU-работу.

Что прозвучало в вашем ответе
  • Вклад проверен сравнением и профилем.
  • Учтены отдельные запросы и выполнение стороннего кода.
  • Выбрана стратегия загрузки с сохранением нужного сценария.

Спецификация: web.dev: сторонний JavaScript

Производительность · Middle и выше

Обработчик клика занимает 10 мс, но взаимодействие отображается только через полсекунды. Почему короткий обработчик не гарантирует хороший INP?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему бы просто не перенести весь обработчик в промис?
  • Включает ли INP полное время сетевой операции до получения результата?

Разбор

На трассе найдите само взаимодействие и разложите задержку на input delay, processing duration и presentation delay. Уменьшение обработчика поможет только если он является существенной частью задержки. Фоновые задачи, синхронная работа других слушателей и большой DOM тоже влияют.

INP оценивает отзывчивость взаимодействий на протяжении посещения, а не только момент первой загрузки. Сохраните быстрый понятный отклик, затем организуйте оставшуюся работу без длинной блокировки. Перенос всего в микрозадачу не гарантирует уступку отрисовке.

Уточняющие вопросы

Почему бы просто не перенести весь обработчик в промис?

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

Включает ли INP полное время сетевой операции до получения результата?

Не обязательно: он связан с задержкой до следующей отрисовки взаимодействия. Полное завершение задачи измеряют отдельно.

Что прозвучало в вашем ответе
  • Названы три части задержки взаимодействия.
  • Причина локализована по трассе, а не только длительности обработчика.
  • INP отличён от полного времени бизнес-операции.

Спецификация: web.dev: оптимизация INP

Производительность · Middle и выше

Главная картинка уже маленькая и быстро скачивается, но LCP остаётся высоким. Какие составляющие LCP проверить до дальнейшего сжатия?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Может ли после оптимизации LCP стать другой элемент?
  • Почему скрытая до завершения анимации картинка может появиться поздно?

Разбор

Сначала убедитесь, какой элемент действительно стал LCP в измерении. Затем сопоставьте получение HTML, обнаружение ресурса, начало и окончание запроса с моментом показа. Причиной может быть позднее создание элемента JavaScript, блокирующие стили или ожидание снятия состояния загрузки.

Оптимизируйте самую значимую фазу. Не приписывайте автоматически медленному серверу задержку, возникшую после получения данных. Для текстового LCP набор ресурсов и причины отличаются, поэтому сначала нужен реальный кандидат.

Уточняющие вопросы

Может ли после оптимизации LCP стать другой элемент?

Да: кандидат зависит от фактического содержимого и времени его отображения; его нужно проверять заново.

Почему скрытая до завершения анимации картинка может появиться поздно?

Загрузка файла и готовность элемента к видимому отображению — разные этапы. Нужно смотреть условия его показа.

Что прозвучало в вашем ответе
  • Определён фактический LCP-элемент.
  • Различены сеть, обнаружение и задержка отображения.
  • Оптимизация выбрана по доминирующей фазе.

Спецификация: web.dev: составляющие LCP

Производительность · Middle и выше

Цикл меняет ширину каждой карточки и сразу читает её offsetHeight. При сотнях карточек интерфейс зависает. Почему и как перестроить работу?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Всегда ли чтение offsetHeight дорогое?
  • Что выбрать, если размер надо получать при его изменении?

Разбор

Запишите профиль и найдите forced layout. Если нужны старые размеры, сначала соберите все измерения, затем примените изменения. Если нужны новые размеры после общей перестройки, сначала выполните записи, затем считайте результаты одной фазой.

Не меняйте порядок вслепую, если вычисление действительно зависит от предыдущего шага: нужно сохранить семантику. Иногда задачу решает CSS-раскладка без ручных измерений. Само помещение прежнего цикла в requestAnimationFrame не устраняет чередование чтений и записей внутри него.

Уточняющие вопросы

Всегда ли чтение offsetHeight дорогое?

Нет: проблема возникает, когда актуальная геометрия ещё не вычислена после изменений. Стоимость подтверждают профилем.

Что выбрать, если размер надо получать при его изменении?

Рассмотреть ResizeObserver, учитывая, что запись размеров из callback тоже может создать цикл обновлений.

Что прозвучало в вашем ответе
  • Объяснён принудительный пересчёт после записи.
  • Чтения и записи сгруппированы с учётом зависимости данных.
  • Исправление проверено по профилю.

Спецификация: web.dev: layout thrashing

Производительность · Middle и выше

Таблица содержит 50 000 строк: данные уже в памяти, но DOM делает прокрутку медленной. Чем виртуализация отличается от пагинации и что она может сломать?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему локальный черновик строки может исчезнуть при прокрутке?
  • Зачем запас строк вокруг видимой области?

Разбор

Измерьте число узлов, стоимость рендера и раскладки. При виртуализации невидимые строки отсутствуют, хотя данные могут оставаться загруженными. Это влияет на поиск браузера, выделение текста, печать и работу вспомогательных технологий.

Для переменной высоты строк нужны измерения и корректировка положения. Стабильные ключи, сохранение редактируемого состояния вне временно удаляемой строки и стратегия фокуса обязательны. Виртуализация не ускоряет автоматически фильтрацию всех данных на каждом вводе.

Уточняющие вопросы

Почему локальный черновик строки может исчезнуть при прокрутке?

Строка может размонтироваться за пределами окна; важное состояние нужно сохранять по ID независимо от её DOM.

Зачем запас строк вокруг видимой области?

Он уменьшает риск пустых промежутков при быстрой прокрутке, но слишком большой запас снижает выигрыш.

Что прозвучало в вашем ответе
  • Описано ограниченное окно DOM.
  • Учтены размеры, стабильные ID и состояние строк.
  • Названы ограничения поиска, фокуса и доступности.

Спецификация: web.dev: виртуализация списков

Производительность · Middle и выше

Длинная статья тяжело отрисовывается. Можно ли применить content-visibility: auto вместо удаления невидимых разделов из DOM и что нужно проверить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Заменяет ли это свойство сетевую ленивую загрузку?
  • Почему важна оценка размера?

Разбор

Применяйте свойство к достаточно самостоятельным разделам, а не механически к каждому элементу. В отличие от виртуализации, DOM и данные не исчезают, поэтому экономия памяти и возможности поиска отличаются.

Неточный резерв размера способен менять длину страницы и положение прокрутки при раскрытии реальной геометрии. Скрипты, постоянно требующие точных измерений пропущенных областей, могут уменьшить пользу. Проверьте переходы по якорям, поиск, фокус, доступность и печать.

Уточняющие вопросы

Заменяет ли это свойство сетевую ленивую загрузку?

Нет: пропуск части рендеринга не является общим запретом загрузки изображений или данных.

Почему важна оценка размера?

Без разумного резерва раскладка и прокрутка могут заметно меняться по мере обработки разделов.

Что прозвучало в вашем ответе
  • Пропуск рендеринга отличён от удаления DOM.
  • Учтён резерв размера.
  • Проверены навигация, измерения и пользовательские сценарии.

Спецификация: web.dev: content-visibility

Производительность · Middle и выше

Чтобы ускорить анимации, всем карточкам добавили will-change: transform. Памяти стало больше, а телефон начал тормозить. Почему подсказка может навредить?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Гарантирует ли will-change отдельный GPU-слой?
  • Почему большой слой может быть дороже маленького?

Разбор

Сравните работу без подсказки и с ней: главные затраты могут находиться в JavaScript, paint или больших поверхностях, а не в создании слоя. transform часто позволяет избежать раскладки, но не обещает нулевой стоимости отображения.

Если подсказка действительно полезна, включайте её заранее для ограниченного числа активных элементов и убирайте, когда она не нужна. Проверьте память, плавность и визуальное поведение: некоторые значения также влияют на контексты наложения.

Уточняющие вопросы

Гарантирует ли will-change отдельный GPU-слой?

Нет: это подсказка, конкретное решение принимает браузер.

Почему большой слой может быть дороже маленького?

Для него нужны большие поверхности и больше памяти и обработки пикселей; число элементов само по себе не описывает стоимость.

Что прозвучало в вашем ответе
  • Подсказка не названа гарантией ускорения.
  • Учтены память и стоимость слоёв.
  • Применение ограничено измеренным сценарием.

Спецификация: web.dev: рендеринг и кадры

Производительность · Middle и выше

Нужно узнать, сколько занимает открытие отчёта: запрос, преобразование данных и показ таблицы. Как расставить измерения, чтобы не смешать эти этапы?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему не использовать только Date.now()?
  • Что измерять при ошибке запроса?

Разбор

performance.mark создаёт отметку, performance.measure измеряет интервал между отметками. Используйте уникальную связь с операцией, чтобы параллельные открытия не смешали начало одного запроса и конец другого. Отдельно фиксируйте отказ и отмену.

Метка после функции рендера означает завершение её JavaScript, но не обязательно показ пикселей: сопоставьте её с трассировкой браузера. Удаляйте ненужные записи при длительной работе. В телеметрию передают метрики и безопасные технические признаки, а не содержимое отчёта.

Уточняющие вопросы

Почему не использовать только Date.now()?

Для измерения длительностей предпочтительнее монотонные часы Performance API, не привязанные к изменениям системного календарного времени.

Что измерять при ошибке запроса?

Длительность неуспешной операции и её исход отдельно от успешных результатов, иначе статистика будет смещена.

Что прозвучало в вашем ответе
  • Этапы измеряются раздельно через отметки.
  • Параллельные операции и исходы не смешиваются.
  • Завершение JS не выдано за показ кадра.

Спецификация: W3C: User Timing

Производительность · Middle и выше

После каждой новой функции страница становится немного тяжелее. Как поставить ограничения в CI, чтобы ловить регрессии, не гоняясь за одним баллом Lighthouse?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему один лимит общего размера сайта неудобен?
  • Что делать при шумном измерении времени в CI?

Разбор

Например, отдельно отслеживают начальный JS, критические изображения и задержку открытия каталога на выбранном устройстве. Порог выбирают по целям продукта и базовым измерениям, а не по универсальному числу для любого сайта.

Сравнивайте с базовой веткой или утверждённым бюджетом, сохраняйте отчёты и учитывайте разброс лабораторных измерений. Осознанное увеличение возможно, если польза оправдана и последствия понятны. После релиза проверяйте реальные данные: лабораторный бюджет не описывает всё поведение аудитории.

Уточняющие вопросы

Почему один лимит общего размера сайта неудобен?

Не все ресурсы нужны в первом сценарии; он может скрывать рост начального пакета среди редко загружаемых файлов.

Что делать при шумном измерении времени в CI?

Стабилизировать окружение, повторять прогоны и использовать обоснованный допуск, а не бесконечно повышать лимит.

Что прозвучало в вашем ответе
  • Бюджеты связаны с конкретными сценариями.
  • Раздельно учитываются ресурсы и время.
  • Есть базовое сравнение, допуск и разбор превышения.

Спецификация: web.dev: бюджеты производительности

Производительность · Middle и выше

Поиск по 100 000 записям на каждый символ нормализует строки, сортирует весь массив и создаёт много временных объектов. Что проверить до переноса вычисления в Worker?

Ответьте вслух или запишите тезисы, затем откройте разбор. Записи сохраняются в этом браузере до конца подхода.

Интервьюер может уточнить

  • Почему нельзя кешировать результат без учёта версии данных?
  • Всегда ли индекс полезнее простого перебора?

Разбор

Запишите профиль вычислений и выделений памяти. Постоянное преобразование одинаковых строк и сортировка всего набора могут стоить больше, чем сам поиск. Для устойчивого набора данных можно подготовить индекс или нормализованное представление с понятным правилом обновления.

Проверьте корректность языка, порядка результатов и инвалидирования кеша. Сравните время и память до и после. Если работа всё ещё тяжёлая, вынесите её в Worker с ограниченным обменом; устаревший результат не должен заменять актуальный запрос.

Уточняющие вопросы

Почему нельзя кешировать результат без учёта версии данных?

После обновления каталога кеш может вернуть устаревшие элементы или порядок.

Всегда ли индекс полезнее простого перебора?

Нет: индекс занимает память, строится и обновляется. Выигрыш зависит от числа запросов, размера и частоты изменения данных.

Что прозвучало в вашем ответе
  • Измерены вычисления и повторная работа.
  • Предложена оптимизация данных или алгоритма с оценкой памяти.
  • Учтены актуальность кеша и стоимость Worker.

Спецификация: Chrome DevTools: анализ работы JavaScript

Разбор описывает один из допустимых ответов; сверяйтесь с пунктами чек-листа, а не с точной формулировкой. Ссылки на уроки сайта и спецификации — в конце каждого разбора.