Реальные собеседования Middle/Senior: Frontend и Java — что спрашивают
Мы разобрали два реальных технических интервью — одно для Frontend-разработчика (React/TypeScript, вилка 260–290K рублей на руки), второе для Java Middle/Senior. Суммарно около двух часов живого контента: вопросы, live coding, разбор ошибок, фидбэк.
Статья для тех, кто готовится к смене работы или хочет понять, где именно «сыпятся» кандидаты на этих уровнях. Не пересказ теории из документации — конкретные вопросы, конкретные задачи, конкретные грабли.
Главный вывод: оба интервью показывают одно и то же — глубина важнее ширины. Кандидат, который знает три темы по-настоящему хорошо, выглядит убедительнее того, кто знает десять тем поверхностно.
Что спрашивают на Frontend Middle/Senior: структура интервью
Собеседование делилось на две части — теория и live coding. Теория шла блоками: браузер → JavaScript → React → TypeScript. Live coding — четыре задачи разной сложности.
Важный момент по структуре: вопросы шли от общего к частному. Сначала «что такое браузер», потом «как работает DOM», потом «что такое CORS и кто его настраивает». Это не случайность — интервьюер проверял, умеет ли кандидат выстраивать цепочку понятий, а не просто воспроизводить определения.
Браузер и сеть: базовый блок, который режет кандидатов
Вопросы по браузеру кажутся простыми — и именно поэтому здесь легко потерять очки.
DOM — это API, представляющее HTML-документ в виде дерева объектов. Кандидат должен не просто знать определение, но и уверенно называть методы: getElementById, querySelector, querySelectorAll, remove. На практике это проверяется быстро.
CORS — механизм браузера, который через HTTP-заголовки решает, может ли страница с одного домена запросить ресурс с другого. Ключевой момент, который часто путают: настройкой CORS занимается бэкенд, не фронтенд. Браузер только исполняет правила. Под капотом CORS использует preflight-запрос методом OPTIONS — это стоит знать и уметь объяснить.
WebSockets — протокол для двустороннего соединения поверх TCP. Отличие от обычного HTTP: одно постоянно открытое соединение вместо отдельного соединения на каждый запрос. Классика применения — чаты, live-уведомления, биржевые тикеры.
HTTP-методы — GET, POST, PATCH, PUT, DELETE, OPTIONS. Разница между PATCH и PUT — частый уточняющий вопрос: PATCH обновляет часть ресурса, PUT заменяет его целиком.
JavaScript: где кандидаты теряют баллы
Восемь типов данных в JS — это не «восемь», это конкретный список, который нужно воспроизвести без запинок. Семь примитивов: number, string, boolean, null, undefined, bigint, symbol. Один непримитивный — object.
Два типа из этого списка регулярно вызывают затруднения:
BigInt— для целых чисел больше 2⁵³ − 1. Обычныйnumberне справляется с такими значениями из-за ограничений IEEE 754.Symbol— уникальное неизменяемое значение, используется как ключ в объектах. ДваSymbol()с одинаковым описанием не равны между собой.
Event Loop — один из самых частых вопросов на Middle и выше. Суть: сначала выполняется весь синхронный код, затем очередь микрозадач (промисы, queueMicrotask), затем макрозадачи (setTimeout, setInterval). На интервью дают код и просят назвать порядок вывода в консоль. Разберём по пунктам на примере из разобранного материала:
// Порядок вывода: 1, 6 → 3, 2 → 5
// Синхронный код → микрозадачи (промисы) → макрозадачи (setTimeout)
Это не абстрактная теория — это конкретный навык, который проверяется кодом.
Сборка мусора — алгоритм «пометок и очистки» (mark-and-sweep): движок помечает все достижимые объекты, недостижимые удаляет. Ускорить или отключить этот процесс нельзя. Для сравнения: в C/C++ разработчик управляет памятью вручную — это и мощь, и источник утечек.
localStorage vs sessionStorage — данные в localStorage живут после закрытия браузера, в sessionStorage — только в рамках вкладки/сессии. Очистка через DevTools → Application или программно через removeItem, clear.
React: Virtual DOM, мемоизация и ловушки с Context
Virtual DOM — виртуальная копия реального DOM, хранящаяся в памяти как объект. Работает в три этапа: первоначальный рендеринг (создание виртуального дерева при монтировании), reconciliation (сравнение нового и старого виртуального дерева), коммит (применение изменений к реальному DOM). Смысл всего этого — минимизировать дорогостоящие операции браузера: layout, painting, reflow.
Сравнение React, Vue, Angular — стандартный вопрос. Коротко:
| React | Vue | Angular | |
|---|---|---|---|
| Тип | Библиотека | Фреймворк | Фреймворк |
| DOM | Virtual DOM | Virtual DOM | Incremental DOM |
| State | Redux Toolkit / MobX | Pinia | NgRx |
Мемоизация — здесь кандидаты часто знают «что», но не знают «когда». Три инструмента:
useMemo— кэширует результат вычисления, принимает функцию и массив зависимостейuseCallback— кэширует саму функцию, чтобы не пересоздавать её при каждом рендереReact.memo— HOC, предотвращает ре-рендер компонента при неизменившихся пропсах
Ключевой момент, который часто упускают: если пропсы меняются при каждом рендере — мемоизация не даёт выигрыша. Она добавляет накладные расходы на сравнение и при этом ничего не кэширует. Применять useMemo и useCallback везде подряд — антипаттерн.
Context vs Redux — частый вопрос. Context передаёт данные через дерево без явной передачи пропсов. Главный минус: при изменении значения в контексте перерендериваются все подписанные компоненты. Redux (точнее Redux Toolkit) даёт глобальное хранилище с более тонкой подпиской — компонент перерендерится только при изменении конкретного среза состояния.
TypeScript: interface vs type — вечный вопрос
TypeScript — статически типизированная надстройка над JS. Компилируется в обычный JS перед запуском, поэтому от runtime-ошибок не спасает. Это важно понимать: TypeScript работает на этапе разработки, не в продакшне.
Разница между interface и type:
interface— для описания структур объектов и сущностей. Например, структура пользователя с полямиname,role,id. Интерфейс может наследоваться от другого интерфейса.type— для союзных типов и алиасов. Например, полеstatusможет быть'admin' | 'banned' | 'deleted'. Тип не поддерживает наследование так же, как интерфейс.
Когда TypeScript избыточен: быстрые прототипы, pet-проекты без долгосрочной перспективы. В таких случаях накладные расходы на типизацию не окупаются.
Live coding на Frontend: четыре задачи и их разбор
Практическая часть — четыре задачи. Разберём каждую.
sortById — функция принимает массив объектов с полем id, возвращает новый массив, отсортированный по возрастанию. Решение: spread-оператор для создания нового массива, затем sort с компаратором по полю id. Задача на знание иммутабельности — исходный массив трогать нельзя.
Event Loop — код с синхронными блоками, промисами и setTimeout. Нужно назвать порядок вывода. Проверяет понимание очередей задач, не синтаксис.
simplifyPath — упрощение Unix-пути: убрать двойные слэши, обработать . (текущая директория) и .. (переход на уровень выше). Решение через стек: разбить путь по /, пройтись по частям, пропускать пустые строки и ., при .. делать pop (если стек не пуст), иначе push. В конце — join('/') и добавить ведущий слэш. Задача на алгоритмическое мышление, а не на знание React.
customEvery и customFlat — реализация стандартных методов массива вручную. customEvery принимает массив и колбэк, возвращает false при первом несоответствии. customFlat — рекурсивная реализация с параметром глубины, Array.isArray для проверки вложенности.
Паттерн очевиден: интервьюер проверяет не знание библиотек, а умение думать алгоритмически и писать чистый код под давлением времени.
Что спрашивают на Java Middle/Senior: восемь блоков за час
Java-интервью оказалось плотнее по охвату. Восемь тематических блоков за 67 минут — это высокий темп. Разберём по блокам.
Коллекции: иерархия и внутреннее устройство
Иерархия: Iterable → Collection → List / Queue / Set. Map стоит отдельно от этой иерархии — это частая ловушка.
ArrayList vs LinkedList — классика, но с нюансом. ArrayList хранит данные в непрерывной области памяти — процессор кэширует их эффективно. LinkedList хранит элементы в разных участках памяти, связанных указателями. На практике ArrayList быстрее даже при вставке в середину, потому что при создании резервирует пространство: дефолтный размер 10 элементов, при заполнении расширяется в 1.5 раза. Часть индексов уже свободна — сдвигать нужно меньше элементов, чем в теории.
HashMap — внутреннее устройство нужно знать детально:
putвычисляет хэш-код ключа- Через остаток от деления определяется номер бакета
- Если бакет пуст — элемент помещается сразу
- Если занят — сравниваются хэш-коды (быстро), затем вызывается
equals(медленнее) - При коллизии элементы выстраиваются в связный список внутри бакета
- При достижении 8 элементов в бакете — перестройка в красно-чёрное дерево, сложность поиска улучшается с O(n) до O(log n)
Многопоточность: synchronized, Lock и дедлоки
ExecutorService — четыре реализации:
- SingleThreadExecutor — один поток
- FixedThreadPool — фиксированное число потоков
- CachedThreadPool — динамически создаёт потоки, простаивающий поток живёт 60 секунд по умолчанию
- ScheduledThreadPool — для задач по расписанию
Смысл пула потоков: создание нового потока — дорогая операция (выделение памяти, регистрация в ОС). Пул переиспользует уже созданные.
synchronized vs Lock — synchronized прост, но негибок. ReentrantLock даёт больше контроля:
- tryLock(timeout) — поток пытается захватить монитор в течение заданного времени, затем отступает
- параметр fairness — монитор получает поток, который ждёт дольше всех, что предотвращает thread starvation (голодание потоков)
Важное предупреждение: synchronized на статическом методе блокирует монитор всего класса. Это нежелательная практика — последствия при использовании сторонними компонентами непредсказуемы.
Дедлок — два потока захватывают ресурсы в обратном порядке и блокируют друг друга навсегда. Решение: захватывать ресурсы всегда в одном порядке, либо использовать tryLock. Лайвлок — потоки активны, но не продвигаются вперёд, постоянно уступая друг другу. Аналогия: два человека в узком коридоре, которые одновременно пытаются уступить дорогу.
Базы данных: индексы, изоляция, MVCC
Индексы — два основных типа в PostgreSQL: - B-Tree — покрывает ~95% случаев, поддерживает поиск по диапазону и точному значению - Hash — быстрый поиск по точному значению, диапазоны не поддерживает
Селективность — ключевое понятие. Столбец с двумя возможными значениями (например, пол) — индекс бессмысленен. Столбец с уникальными значениями (например, email) — индекс максимально эффективен.
Составной индекс — работает как вложенные деревья: дерево A → дерево B → дерево C. Если запрос использует только B и C, минуя A — индекс не сработает. Это правило «левого префикса», и его незнание — частая ошибка.
Создавать индексы по всем столбцам — ошибка. При каждой вставке и обновлении все индексы нужно перестраивать. Это замедляет запись.
Уровни изоляции транзакций — четыре уровня, три проблемы: - Грязное чтение — читаем незакоммиченные данные другой транзакции - Неповторяемое чтение — одно и то же значение в рамках транзакции читается дважды с разным результатом - Фантомное чтение — количество строк между двумя чтениями в одной транзакции изменилось
В PostgreSQL уровень Read Uncommitted отсутствует — его заменяет механизм MVCC (многоверсионное управление параллелизмом). Каждая транзакция видит свою версию данных. После удаления строк образуются «мёртвые» версии — их очищает процесс VACUUM.
Spring, Kafka и паттерн Transactional Outbox
@Transactional — два подводных камня:
1. Не работает на приватных методах
2. Не работает при вызове транзакционного метода из другого метода того же класса (self-invocation обходит прокси Spring)
Решение в обоих случаях — вынести метод в отдельный бин.
Kafka и гарантии доставки — три уровня: At Least Once, At Most Once, Exactly Once. Реальная проблема: сервис сохраняет заказ в PostgreSQL и отправляет сообщение в Kafka. Если сервис падает после записи в базу, но до отправки в Kafka — сообщение теряется.
Решение — паттерн Transactional Outbox: сохранять сообщение в отдельную таблицу в рамках той же транзакции, что и основные данные. Отдельный планировщик читает эту таблицу и отправляет сообщения в брокер. Для защиты от дублирования — идемпотентный идентификатор на стороне потребителя.
Практический чек-лист: что делать прямо сейчас
Для Frontend-разработчика
- Браузер и сеть — объясни вслух, как работает CORS, кто его настраивает и зачем нужен preflight. Если запнулся — повторить.
- Event Loop — возьми любой онлайн-визуализатор Event Loop, прогони 5–10 примеров с промисами и
setTimeout. Порядок вывода должен быть очевиден без раздумий. - Типы данных JS — напиши все восемь по памяти. Объясни разницу между
nullиundefined, междуbigintиnumber. - Virtual DOM — нарисуй на бумаге три этапа: первоначальный рендеринг, reconciliation, коммит. Объясни, зачем это нужно.
- Мемоизация — напиши пример, где
useMemoоправдан, и пример, где он бесполезен. Объясни разницу. - TypeScript — возьми реальную структуру данных из своего проекта и опиши её через
interface. Потом черезtype. Объясни, что выбрал и почему. - Live coding — реши
customFlatиcustomEveryбез подсказок. Потом реши задачу на стек (например,simplifyPathили валидация скобок).
Для Java-разработчика
- Коллекции — нарисуй иерархию интерфейсов по памяти. Объясни внутреннее устройство HashMap: бакеты, коллизии, красно-чёрное дерево при 8 элементах.
- Многопоточность — объясни разницу между
synchronizedиReentrantLock. Приведи пример дедлока и скажи, как его избежать. - GC — назови все сборщики в HotSpot JVM. Объясни, почему G1 — дефолтный. Что такое Stop-the-World.
- Индексы — объясни правило левого префикса для составных индексов. Приведи пример столбца с низкой селективностью.
- Транзакции — назови четыре уровня изоляции и три проблемы, которые они решают. Объясни MVCC в двух предложениях.
@Transactional— воспроизведи оба сценария, где аннотация не работает. Скажи, как исправить.- Transactional Outbox — нарисуй схему: база данных → таблица outbox → планировщик → Kafka. Объясни, зачем идемпотентный идентификатор.
Что мы заметили: где подходы совпадают, где расходятся
Оба интервью — разные стеки, разные уровни сложности, но несколько общих паттернов бросаются в глаза.
Где материалы сходятся:
— Интервьюеры в обоих случаях идут от общего к частному. Сначала «что это такое», потом «как работает внутри», потом «где ломается». Кандидат, который знает только определение, — не проходит.
— Практические задачи важнее теории. В обоих интервью живой код занимал значительную часть времени. Умение думать вслух и объяснять решение — отдельный навык, который оценивается.
— Типичные ошибки — не в сложных темах. Кандидаты спотыкаются на мемоизации (применяют везде), на @Transactional (self-invocation), на индексах (создают на все столбцы). Это не rocket science — это детали, которые нужно знать.
Где подходы расходятся:
— Более консервативный взгляд (прослеживается в Frontend-интервью): акцент на базовых концепциях браузера и JS. Если кандидат не может объяснить Event Loop — дальнейшие вопросы по React теряют смысл.
— Более системный взгляд (Java-интервью): интервьюер явно ожидает понимания архитектурных паттернов и компромиссов. Вопрос «а что если сервис упадёт?» — это не теория, это проверка инженерного мышления. Паттерн Transactional Outbox здесь — не бонус, а ожидаемый ответ на уровне Middle+.
— В Frontend-интервью достаточно знать инструмент и уметь его применять. В Java-интервью от кандидата ожидают понимания, почему инструмент работает именно так — вплоть до внутреннего устройства HashMap и механизма MVCC в PostgreSQL.
Итог
Оба интервью подтверждают одну вещь: на уровне Middle/Senior «знаю, что это такое» — недостаточно. Нужно «знаю, как это работает внутри» и «знаю, где это ломается».
Хорошая новость: большинство вопросов из обоих интервью — конечный список. Коллекции Java, Event Loop, Virtual DOM, уровни изоляции транзакций — это не бесконечная область. Это конкретные темы, которые можно проработать за несколько недель систематической подготовки. Возьмите чек-лист выше, закройте одну тему за раз, и через месяц картина будет другой.