Вопросы на собеседовании: Инженер-программист
100 реальных вопросов с образцовыми ответами и пояснениями для уровня Middle.
Смотреть пример резюме: Инженер-программист →Тренировка флешкарточками
Интервальное повторение · Hunter Pass
Вопросы
Я рассматриваю это как связанную с деплоем регрессию производительности и локализую её с помощью метрик, трейсов и контролируемого сравнения.
- Я начинаю с того, что сравниваю diff деплоя и сопоставляю скачок задержки с точным временем выката на дашборде метрик, потом смотрю p50 против p99, чтобы понять, идёт ли речь об общем сдвиге или о регрессе в хвосте.
- Отсутствие ошибок обычно говорит о добавленной работе, а не о сбоях, поэтому я ищу новые синхронные вызовы, N+1 запросы, потерянный кэш или изменение конфига вроде размера пула соединений.
- Я вытягиваю трейсы медленных запросов, чтобы найти, какой спан вырос, и при необходимости гоняю старую и новую сборку рядом под одинаковой нагрузкой, чтобы подтвердить регресс.
Зачем это спрашивают: Интервьюер проверяет, связывает ли кандидат изменение с деплоем и рассуждает ли про добавленную работу против сбоев, а не гадает наугад.
Я различаю упор в CPU и I/O по профилям, метрикам загрузки и трейсам, а уже затем выбираю решение.
- Я проверяю, совпадает ли процессорное время с реальным временем запроса, сэмплируя профайлером вроде py-spy или perf: если время на CPU занимает большую часть длительности запроса, значит упор в CPU, а если поток в основном ждёт сокеты или диск, значит упор в I/O.
- Метрики тоже помогают, ведь высокая загрузка CPU при низком iowait говорит про вычисления, а низкий CPU с длинными спанами внешних вызовов в трейсах указывает на I/O.
- Решения разные: работа с упором в CPU требует улучшения алгоритмов, кэширования или параллелизма, а работа с упором в I/O требует батчинга, пула соединений, конкурентности или устранения лишних round trip.
Зачем это спрашивают: Сильный ответ различает два случая по данным профайлера и метрик и сопоставляет каждый со своим классом решения.
Я ищу утечку памяти по росту удерживаемого хипа, прослеживаю владельца объектов и подтверждаю исправление длительной нагрузкой.
- Я снимаю хип-снапшоты через интервалы и сравниваю их, чтобы увидеть, какие типы объектов продолжают расти, используя инструменты вроде Chrome DevTools или хип-профайлера Node для JS, либо tracemalloc и objgraph для Python.
- Постоянно растущий RSS вместе с растущим удержанным набором обычно означает, что где-то держатся ссылки, часто это кэши без вытеснения, неудалённые обработчики событий или неограниченные очереди.
- Найдя растущий тип, я прослеживаю путь удержания до владеющей структуры, чиню удержание и подтверждаю soak-тестом, что память выходит на плато.
Зачем это спрашивают: Здесь оценивают, использует ли кандидат сравнение хип-снапшотов для поиска растущих объектов, а не просто перезапускает сервис.
Баг, исчезающий при наблюдении, я считаю вероятным тайминг-зависимым гейзенбагом и исследую инструментами с малыми накладными расходами.
- Это классический гейзенбаг, и обычно он означает проблему тайминга или конкурентности, где логирование достаточно меняет расписание, чтобы скрыть гонку, либо отладчик сериализует потоки, которые обычно чередуются.
- Причиной также бывает неинициализированная память или оптимизация компилятора, которую меняет отладочная сборка.
- Я действую так: добавляю наблюдение с низкими накладными расходами вроде счётчиков, кольцевых буферов или thread sanitizer вместо блокирующих логов, и нагружаю конкурентность большим числом потоков и случайными задержками, чтобы гонка стала воспроизводимой.
Зачем это спрашивают: Интервьюер ждёт распознавания эффектов конкурентности и тайминга и наблюдения с малыми накладными расходами, а не растерянности.
Я профилирую реалистичную нагрузку и оптимизирую самые широкие значимые горячие пути, а не визуально заметные фреймы.
- Я запускаю сэмплирующий профайлер под реалистичной нагрузкой и читаю flame graph, ища самые широкие фреймы, которые дают наибольшую долю общего времени, а не самые глубокие стеки.
- Частые ошибки: профилировать прогрев или нерепрезентативную нагрузку, доверять одному прогону вместо агрегации, путать self time с общим временем и оптимизировать функцию, которая выглядит горячей, но занимает всего 2 процента реального трафика.
- Ещё я слежу за эффектом наблюдателя от тяжёлой инструментации и предпочитаю сэмплинг полной трассировке ради чисел, близких к продакшену.
Зачем это спрашивают: Сильный ответ правильно читает flame graph и называет типичные ловушки профилирования, которые слабый упускает.
Я воспроизвожу сбой под контролируемой нагрузкой, а затем ищу конкуренцию, насыщение и лимиты ресурсов.
- Сначала я воспроизвожу нагрузку инструментом вроде k6, Locust или wrk, чтобы проблема стала наблюдаемой, потом слежу за сигналами насыщения: исчерпание пула потоков и пула соединений, конкуренция за блокировки, паузы GC и глубина очередей.
- Проблемы, которые видны только под нагрузкой, обычно вызваны конкуренцией или лимитами ресурсов, а не логическими багами, поэтому я смотрю на размеры пулов, таймауты и backpressure.
- Непрерывное профилирование и трейсы отдельных запросов во время нагрузочного теста показывают, где концентрируются время и ожидание по мере роста конкурентности.
Зачем это спрашивают: Здесь проверяют, воспроизводит ли кандидат нагрузку и рассуждает ли про насыщение и конкуренцию, а не про логические баги.
Я прослеживаю запрос от начала до конца по проброшенному trace id, а при необходимости связываю логи по request id.
- Я использую распределённую трассировку с correlation или trace id, который пробрасывается через границы сервисов, например через OpenTelemetry и заголовки W3C trace context, чтобы один запрос давал единый связанный трейс в Jaeger или Tempo.
- Я ищу по этому trace id, вижу всё дерево спанов, нахожу, на каком хопе была ошибка или таймаут, и читаю теги со статус-кодами и ретраями.
- Если трассировки нет, я откатываюсь на request id, логируемый в каждом сервисе, и сшиваю логи в агрегаторе.
Зачем это спрашивают: Интервьюер ждёт correlation id и распределённой трассировки, а не ручного гадания по сервисам.
Метрики помогают мне обнаружить проблему, трейсы локализовать её, а логи объяснить конкретное событие.
- Метрики представляют собой дешёвые агрегированные числа во времени, которые отвечают, есть ли проблема и как часто, поэтому я использую их для дашбордов и алертов.
- Логи представляют собой отдельные события с деталями, которые отвечают, что именно произошло в конкретном случае, и полезны, когда алерт сработал и нужен контекст.
- Трейсы показывают путь и тайминг одного запроса по компонентам, отвечая, где живёт задержка или сбой, и вместе они складываются в поток: алерт по метрике, поиск запроса через трейс, чтение логов для деталей.
Зачем это спрашивают: Сильный ответ объясняет отдельную роль каждого и как они сочетаются, а не только даёт определения.
Я подтверждаю баг зависимости изолированным воспроизведением до эскалации или добавления обходного решения.
- Сначала я изолирую подозреваемого, написав минимальное воспроизведение, которое вызывает библиотеку напрямую с теми же входными данными, исключая мой код-обёртку.
- Потом я проверяю трекер задач и changelog библиотеки на известные баги в моей версии, фиксирую и бисекчу версии, чтобы найти, где поменялось поведение, и читаю нужный исходник, раз он доступен.
- Если подтвердилось, я завожу issue в апстрим с воспроизведением, применяю локальный обходной путь или патч вроде форка или monkeypatch и фиксирую безопасную версию, пока не выйдет фикс.
Зачем это спрашивают: Здесь оценивают дисциплинированную изоляцию и минимальное воспроизведение до того, как винить зависимость.
Каждую гипотезу о таймаутах базы я связываю с конкретным измерением и сопоставляю данные со временем всплесков.
- Мои первые гипотезы: исчерпание пула соединений, конкуренция за блокировки или долгие транзакции, отсутствующий индекс, вызывающий редкие полные сканы, и насыщение ресурсов на хосте базы.
- Я проверяю их метриками использования пула, логом медленных запросов и pg_stat_activity для блокировок, EXPLAIN ANALYZE по подозрительным запросам и графиками CPU, IO и памяти хоста во время всплесков.
- Периодичность обычно значит корреляцию с чем-то, поэтому я совмещаю таймауты с пиками трафика, батч-джобами или ожиданием блокировок, чтобы найти триггер.
Зачем это спрашивают: Интервьюер проверяет конкретные гипотезы в паре с конкретными инструментами для подтверждения или отклонения каждой.
Я выбираю состав тестов по риску, сложности границ, скорости обратной связи и критичности пользовательских сценариев.
- Я держусь идеи пирамиды тестов: много быстрых модульных тестов на логику и краевые случаи, крепкий слой интеграционных тестов для частей, что касаются реальных границ вроде базы или очереди, и тонкий набор end-to-end тестов для нескольких критичных пользовательских сценариев.
- Баланс зависит от того, где живут риск и сложность, поэтому сервис с упором на оркестрацию и I/O требует больше интеграционных тестов, чем библиотека чистых вычислений.
- Я оптимизирую уверенность на секунду прогона и избегаю перекошенного вверх набора медленных, флаки end-to-end тестов.
Зачем это спрашивают: Сильный ответ рассуждает про риск и стоимость прогона, а не зубрит пирамиду наизусть.
В обычных тестах я заменяю внешний API контролируемыми дублёрами, сохраняя небольшой отдельный набор реальных интеграционных проверок.
- Я изолирую внешний вызов за интерфейсом и подставляю фейк в тестах, а для HTTP поднимаю stub-сервер вроде WireMock или записанную фикстуру, чтобы реального сетевого вызова не было.
- Контрактные тесты или записанная кассета держат заглушку честной относительно реальной формы API, а небольшое число реальных вызовов я гоняю в отдельном, более редком интеграционном наборе.
- Так обычный прогон остаётся быстрым, детерминированным и без флаки из-за лимитов частоты или простоя.
Зачем это спрашивают: Здесь проверяют, изолирует ли кандидат зависимость и держит ли тесты детерминированными без реальных сетевых вызовов.
Контрактные тесты проверяют, что независимо деплоящиеся потребители и провайдеры по-прежнему согласны о своём интерфейсе.
- Контрактные тесты проверяют, что провайдер и потребитель согласны о форме и семантике их интерфейса, не поднимая обе системы вместе, обычно с инструментом вроде Pact.
- Потребитель записывает свои ожидания как контракт, а провайдер проигрывает их в CI, доказывая, что всё ещё удовлетворяет каждого потребителя.
- Они решают проблему независимо деплоящихся команд, которые молча ломают друг друга, ловя несовместимые изменения до того, как те дойдут до продакшена.
Зачем это спрашивают: Интервьюер ждёт проблему координации, которую решают контрактные тесты, а не только определение инструмента.
Моки ухудшают тесты, когда связывают набор с деталями реализации или неверно изображают реальные зависимости.
- Моки вредят, когда слишком жёстко фиксируют взаимодействия, и тест проверяет, как код работает, а не что он выдаёт, и тогда любой рефакторинг ломает тесты.
- Они также вводят в заблуждение, когда мок расходится с поведением реальной зависимости и даёт зелёные тесты против фантазии, и когда мокаешь типы, которыми не владеешь, так что заглушка кодирует неверные допущения.
- Я предпочитаю мокать на настоящих границах, использовать реальные объекты или фейки для внутрипроцессных коллабораторов и проверять наблюдаемые результаты, а не последовательности вызовов.
Зачем это спрашивают: Сильный ответ знает, что моки могут связать тесты с реализацией и разойтись с реальностью.
Я возвращаю доверие к флаки-набору, отделяя известный шум, устраняя причины и снова делая красную сборку значимым сигналом.
- Я делаю флаки видимой, вынося известные флаки-тесты в отдельную дорожку и отслеживая долю флаки, чтобы основная сборка снова работала по принципу зелёное значит зелёное, а красное реально блокировало мержи.
- Потом я чиню корневые причины по одной: общее состояние и порядок тестов, реальное время и sleep, сетевые и таймингованные гонки, недетерминированный порядок.
- Настоящая цель в том, чтобы вернуть доверие к сигналу, ведь набор, который все игнорируют, не даёт защиты, сколько бы тестов в нём ни было.
Зачем это спрашивают: Здесь оценивают и технический фикс, и восстановление доверия к сигналу сборки.
Я внедряю время как зависимость, чтобы проверять зависящее от него поведение детерминированно и без ожидания.
- Я никогда не читаю системные часы прямо в бизнес-логике, а внедряю абстракцию часов, чтобы тесты могли подать фейковое или фиксированное время и двигать его детерминированно, с инструментами вроде libfaketime или управляемых часов в языке.
- Для запланированных задач я тестирую логику триггера отдельно от расписания и проверяю границы вроде момента прямо перед истечением и сразу после.
- Так истечение токенов, ретраи с backoff и окна cron становятся полностью воспроизводимыми без ожидания реальных секунд.
Зачем это спрашивают: Интервьюер ждёт внедрённых часов вместо sleep и реального времени.
Сначала я защищаю тестами рискованное поведение легаси-кода, а затем создаю швы для более точечных проверок и безопасных изменений.
- Я начинаю с характеризующих тестов, которые фиксируют текущее поведение, даже если оно с причудами, чтобы получить страховку до любых изменений, следуя подходу Майкла Фезерса с поиском швов для разрыва зависимостей.
- Сначала я добавляю тесты на самом высоком уровне, куда дёшево дотянуться, часто вокруг целого модуля, потом опускаю покрытие ниже по мере ввода швов и точек внедрения.
- Приоритет идёт коду, который я собираюсь менять, и самым рискованным путям, а не гонке за числом покрытия по мёртвому коду.
Зачем это спрашивают: Сильный ответ называет характеризующие тесты и швы вместо слепой гонки за покрытием.
Я проверяю обработку ошибок, намеренно создавая отказы и контролируя полное и корректное поведение восстановления.
- Я намеренно вызываю сбои через инъекцию отказов: заставляю мок бросать исключение, возвращаю ответы с ошибками, симулирую таймауты и оборванные соединения и подаю некорректный ввод.
- Я проверяю не только что оно падает, но и что падает правильно: верный статус, чистый откат, отсутствие утечки ресурсов и полезное сообщение, а ещё что ретраи и фолбэки реально срабатывают.
- Инъекция отказов и инструменты вроде проксирующего инжектора сбоев или хаос-экспериментов помогают покрыть пути, которые редко исполняются при нормальной работе.
Зачем это спрашивают: Здесь проверяют намеренную инъекцию отказов и проверку корректного поведения при сбое, а не только что возникла ошибка.
Быстрый набор минимизирует дорогие границы и сохраняет для команды короткую и надёжную петлю обратной связи.
- Скорость идёт от того, что большинство тестов держат в процессе и чистыми, избегают реального I/O, разделяют дорогую подготовку, гоняют параллельно и используют test containers только там, где реальная зависимость необходима.
- Медленные наборы запускают реже, поэтому баги находят позже, а разработчики теряют поток, ожидая, и это тихо разрушает всю петлю обратной связи.
- Я отношусь ко времени набора как к метрике первого класса и закладываю бюджет, ведь набор, который проходит за секунды, гоняют на каждое сохранение, а двадцатиминутный пропускают.
Зачем это спрашивают: Интервьюер ждёт конкретные рычаги скорости плюс почему быстрая обратная связь защищает команду.
Я применяю TDD к понятному нетривиальному поведению и ослабляю подход при реальном исследовании или для одноразового кода.
- Я использую TDD там, где поведение хорошо понятно, а логика нетривиальна: пишу падающий тест, делаю его зелёным просто, потом рефакторю, и это держит дизайн тестируемым, а обратную связь плотной.
- Я пропускаю или ослабляю его, когда исследую неизвестный дизайн или делаю спайк-прототип, где интерфейс ещё плывёт, и для тривиального клеевого кода или одноразовых скриптов.
- Даже тогда я обычно дописываю тесты, когда форма устоялась, чтобы исследование не уехало в продакшен непокрытым.
Зачем это спрашивают: Сильный ответ применяет TDD там, где оно окупается, и честен о том, когда его пропустить.
Закрытые вопросы
- 21
Как спроектировать границу модуля, чтобы другие разработчики пользовались им, не читая внутренности?
design - 22
Вы поддерживаете общую библиотеку, которой пользуются несколько команд, и вам нужно выкатить ломающее изменение. Как вы это сделаете?
versioning - 23
Как проектировать хорошие ответы об ошибках для API?
designapi - 24
Как проектировать списочные эндпоинты с пагинацией, фильтрацией и сортировкой, чтобы они оставались стабильными при изменении данных?
endpointspaginationdesign - 25
Когда вы выберете gRPC или GraphQL вместо обычного REST?
restgraphqlgrpc - 26
Как сделать write-эндпоинт идемпотентным и почему это важно?
endpointsidempotency - 27
Какие есть стратегии версионирования API и каковы их компромиссы?
versioning - 28
Где должна жить валидация входных данных в слоистом приложении и почему?
validation - 29
Какие паттерны проектирования вы реально используете в повседневном коде? Приведите конкретный пример.
design - 30
Как безопасно рефакторить класс на 2000 строк, пока команда продолжает выпускать фичи вокруг него?
refactoring - 31
В чём разница между конкурентностью и параллелизмом? Приведите практический пример каждого.
concurrency - 32
Как защитить общее состояние, к которому обращаются несколько потоков, и какова цена блокировок?
lockingconcurrency - 33
Какие условия порождают взаимоблокировку и как предотвращать deadlock в коде приложения?
locking - 34
Как async/await работает под капотом в рантайме с event loop?
async - 35
Что происходит, когда вы блокируете event loop или исчерпываете пул потоков, и как это заметить?
event-loopconcurrency - 36
Как правильно реализовать таймауты и отмену для долгих операций?
resilience - 37
Два конкурентных запроса читают один и тот же баланс и оба пишут обновление, теряя одно изменение. Как исправить этот класс багов?
concurrency - 38
Когда обеспечивать контроль конкурентности в базе данных, а когда в коде приложения?
databaseconcurrency - 39
Что такое атомарная операция и когда атомиков достаточно вместо блокировок?
concurrency - 40
Как сделать фоновую задачу безопасной для повтора после краха в середине выполнения?
resiliencejobs - 41
SQL-запрос стал медленным по мере роста таблицы. Проведите меня через поиск и устранение причины.
sqlqueries - 42
Что такое проблема N+1 запросов и как обнаружить её в реальном коде?
queriesn+1 - 43
Как работают композитные индексы и почему порядок колонок в них важен?
indexesschema - 44
Что уровни изоляции транзакций означают на практике и какие аномалии предотвращает каждый уровень?
transactions - 45
Когда вы бы денормализовали схему и во что денормализация обходится потом?
schemadenormalization - 46
Как изменить схему большой нагруженной таблицы, не кладя приложение?
schema - 47
Зачем приложения используют пулы соединений с базой и что ломается при плохо подобранном размере пула?
databasepooling - 48
Как паттерны доступа определяют выбор между реляционной базой и key-value или документным хранилищем?
database - 49
Почему OFFSET-пагинация деградирует на больших таблицах и что использовать вместо неё?
pagination - 50
Как конкуренция за блокировки в базе проявляется в поведении приложения и как её снизить?
database - 51
Как убедиться, что бэкапы действительно работают, а не просто существуют?
backups - 52
Ваше приложение иногда читает устаревшие данные сразу после записи, потому что чтения идут в реплику. Что происходит и какие есть варианты?
replication - 53
Какие слои кэширования существуют между пользователем и вашей базой данных и как решить, где кэшировать?
cachingdatabase - 54
Сравните TTL-протухание, write-through и явную инвалидацию для кэшей. Когда каждый из подходов ломается?
caching - 55
Что такое cache stampede и как его предотвратить?
caching - 56
Почему средние значения задержки лгут и что перцентили p95 и p99 говорят такого, чего не говорят средние?
latency - 57
Время ответа сервера выглядит нормально, но пользователи говорят, что продукт кажется медленным. Как улучшить воспринимаемую производительность?
performance - 58
Вы удвоили число инстансов, но пропускная способность почти не выросла. Что проверяете?
throughput - 59
Всё кажется медленным, и у каждого своя теория. Как решить, что оптимизировать первым?
optimization - 60
Как размер полезной нагрузки и сериализация влияют на производительность API и что с этим можно сделать?
serializationapiperformance - 61
Как решить, оставить монолит или разбить на сервисы для продукта в масштабе вашей команды?
monolith - 62
Какие проблемы решает очередь сообщений и какие новые проблемы она вносит?
queuesdata-structures - 63
Что такое итоговая согласованность и где она допустима в типичном продукте?
consistency - 64
Как проектировать повторы (retry) через границы сервисов, чтобы они помогали, а не усиливали сбой?
design - 65
Как не дать фича-флагам превратиться в постоянный технический долг?
tech-debtfeature-flags - 66
Вам нужно вынести одну ограниченную область из монолита. Как сделать это без переписывания одним махом?
monolith - 67
Куда класть бизнес-логику в слоёном приложении и что ломается, когда она протекает в контроллеры или SQL?
sql - 68
Как управлять конфигурацией и секретами между dev, staging и production?
secretsconfig - 69
Что такое плавная деградация и как спроектировать фичу так, чтобы она падала частично, а не полностью?
design - 70
Когда события и асинхронный обмен сообщениями подходят лучше, чем прямые синхронные вызовы между компонентами?
componentsasync - 71
Что содержит хороший CI-пайплайн и в каком порядке вы запускаете проверки?
ci-cd - 72
Как держать CI быстрым по мере роста кодовой базы и набора тестов?
ci-cd - 73
Сравните rolling, blue-green и канареечный деплой. Как выбрать один из них?
deployment-strategiesdeployment - 74
Что делает изменение легко откатываемым и какие изменения откатить трудно?
rollback - 75
В чём разница между проверками liveness и readiness и что происходит, когда вы их путаете?
health-checks - 76
Что и на каком уровне вы логируете и как избежать логов, бесполезных во время инцидента?
incidents - 77
Какие метрики вы бы отдавали для типичного сервиса и как выбирать пороги алертов?
monitoringalerting - 78
Что такое распределённый трейсинг и что он даёт такого, чего не дают логи?
distributed - 79
Какие компромиссы между trunk-based разработкой и долгоживущими фиче-ветками?
git - 80
Как контейнеры меняют то, как вы собираете, доставляете и запускаете приложения?
containers - 81
Как вы ревьюите большой pull request и когда просите его разбить?
code-review - 82
Как доносить критическую обратную связь на ревью, чтобы она была воспринята и код действительно улучшился?
feedback - 83
Коллега стабильно мёржит код со слабыми тестами. Как вы это решите?
testing - 84
Как держать ревью быстрым, не превращая его в формальную печать одобрения?
code-review - 85
Что должно быть в описании pull request и почему это важно для ревьюера?
code-review - 86
Как отделить стилевые предпочтения от существенных проблем на ревью и как поступать с каждым?
soft-skillscode-review - 87
Спроектируйте rate limiter для публичного API. Какой алгоритм и хранилище вы выберете и почему?
rate-limitingdesignalgorithms - 88
Спроектируйте сокращатель URL на высоком уровне. Какие ключевые компоненты и компромиссы?
componentsdesign - 89
Как бы вы спроектировали систему уведомлений, которая шлёт письма и push, не теряя ни одного?
system-designdesign - 90
Продукту нужен поиск по данным. Как выбрать между полнотекстовыми возможностями базы и выделенным поисковым движком?
database - 91
Расскажите о проекте, который вы вели от начала до конца. Как вы довели его от идеи до продакшена?
story - 92
Как вы оцениваете многонедельную работу и что делаете, когда оценка оказывается неверной?
estimation - 93
Расскажите о техническом разногласии с коллегой, которое вы разрешили. Что вы сделали?
storyconflict - 94
Как аргументировать выплату техдолга продакт-менеджеру, сфокусированному на фичах?
tech-debt - 95
Опишите продакшн-инцидент, который вы разруливали. Какова была ваша роль и что изменилось после?
incidents - 96
Вам достался сервис, который никто из оставшихся в команде не понимает. Что вы делаете в первый месяц?
ownership - 97
Как мидл-инженер, как вы балансируете свои задачи и помощь джунам, которые приходят к вам?
mentoring - 98
Расскажите о случае, когда вы выкатили то, чем не гордились, из-за дедлайна. Что было дальше?
storyestimation - 99
Как вы держите расползание объёма под контролем в фиче, которой владеете?
scope - 100
Куда вы хотите расти дальше как инженер и что для этого делаете?
growth