Вопросы на собеседовании: Инженер-программист
100 реальных вопросов с образцовыми ответами и пояснениями для уровня Junior.
Смотреть пример резюме: Инженер-программист →Тренировка флешкарточками
Интервальное повторение · Hunter Pass
Вопросы
Нотация Big O показывает, как время работы или потребление памяти алгоритма растёт вместе с размером входных данных.
- Она отбрасывает константы и младшие члены, чтобы инженеры могли сравнивать подходы независимо от железа.
- Проход за O(n) с ростом данных обгонит вложенный цикл за O(n^2), даже если на маленьком входе второй вариант быстрее.
- Она помогает заранее понять, когда код для 100 строк станет слишком медленным или прожорливым на миллионе строк.
Зачем это спрашивают: Интервьюер проверяет, умеете ли вы рассуждать о масштабируемости абстрактно, а не просто мерить время на маленьких входах.
Массив удобен для быстрого доступа по индексу, а связный список для вставки или удаления уже известных узлов.
- Массив хранит элементы непрерывно, поэтому даёт доступ по индексу за O(1) и эффективно использует кэш.
- Вставка в середину массива стоит O(n) из-за сдвига элементов, а изменение связного списка у известного узла занимает O(1).
- Доступ к k-му узлу списка требует O(n), поэтому массив выбирают для произвольного доступа, а список для частых изменений в известных позициях.
Зачем это спрашивают: Сильный ответ связывает раскладку в памяти с конкретной стоимостью доступа, а слабый просто пересказывает определения без компромисса O(1) против O(n).
Хеш-таблица достигает среднего поиска за O(1), вычисляя по ключу индекс корзины в базовом массиве.
- Прямой переход к корзине избавляет от просмотра всех записей.
- Равномерное хеширование и низкий коэффициент заполнения оставляют в каждой корзине лишь несколько записей.
- O(1) является средней оценкой, а не гарантией, поскольку плохой хеш или высокая заполненность могут ухудшить поиск до O(n).
Зачем это спрашивают: Это проверяет, понимаете ли вы, что O(1) зависит от хорошего распределения хеша и коэффициента заполнения, а не работает как по волшебству.
Коллизия в хеш-таблице возникает, когда разные ключи попадают в один индекс корзины.
- При раздельном сцеплении конфликтующие записи хранятся в небольшой коллекции внутри корзины и просматриваются при поиске.
- При открытой адресации таблица проверяет другие ячейки с помощью линейного, квадратичного или двойного хеширования.
- Java HashMap использует сцепление и может превращать длинные цепочки в сбалансированные деревья, сохраняя худший поиск около O(log n).
Зачем это спрашивают: Интервьюер хочет увидеть, что вы понимаете неизбежность коллизий и можете назвать хотя бы одну конкретную стратегию их разрешения, а не считаете, что ключи никогда не совпадают.
Стек извлекает элементы по принципу LIFO, а очередь по принципу FIFO.
- Стек первым возвращает последний добавленный элемент, как при вложенных вызовах функций или переходе назад в браузере.
- Очередь первой возвращает самую раннюю задачу, как в очереди печати или пуле воркеров.
- Обе структуры могут давать вставку и удаление за O(1), но забирают элементы с разных концов последовательности.
Зачем это спрашивают: Сильный кандидат сопоставляет каждой структуре настоящий сценарий использования, показывая, что связывает абстрактный порядок с реальными системами.
Бинарный поиск находит значение в отсортированных данных, отбрасывая половину оставшегося диапазона после каждого сравнения.
- Он сравнивает искомое значение со средним элементом и работает за O(log n).
- Данные должны быть заранее отсортированы по ключу поиска, иначе нельзя правильно выбрать отбрасываемую половину.
- Предварительная сортировка стоит O(n log n), поэтому бинарный поиск особенно выгоден для повторных запросов к одним отсортированным данным.
Зачем это спрашивают: Забыть про условие отсортированности значит допустить классическую ошибку, которую этот вопрос призван выявить.
Поиск в сбалансированном двоичном дереве занимает O(log n), потому что каждое сравнение отбрасывает целое поддерево.
- Число шагов поиска ограничено полной высотой дерева от корня до листа.
- В сбалансированном дереве высота остаётся пропорциональной log n.
- Разбалансированное дерево может выродиться в связный список с поиском за O(n), поэтому AVL- и красно-чёрные деревья поддерживают баланс.
Зачем это спрашивают: Различие между сбалансированным O(log n) и вырожденным O(n) как раз и отличает точный ответ от размытого.
Рекурсия представляет собой решение задачи через вызов той же функции для уменьшенной версии этой задачи.
- Базовый случай должен гарантированно останавливать дальнейшую цепочку рекурсивных вызовов.
- Рекурсивный случай должен уменьшать вход и приближаться к базовому случаю.
- Отсутствующий или недостижимый базовый случай вызывает бесконечные вызовы и переполнение стека, поскольку каждый вызов расходует память.
Зачем это спрашивают: Назвать и базовый случай, и продвижение к нему показывает, что вы понимаете, почему рекурсия завершается, а не просто как она выглядит.
Дерево является ограниченным видом графа, а граф может описывать более общие связи.
- Граф состоит из узлов и рёбер и может быть ориентированным или неориентированным, с циклами или без них.
- Дерево связно, не содержит циклов и имеет ровно один путь между каждой парой узлов.
- В дереве из n узлов есть n минус одно ребро, поэтому каждое дерево является графом, но не каждый граф является деревом.
Зачем это спрашивают: Сильный ответ подаёт дерево как ограниченный граф, показывая, что вы улавливаете иерархию, а не считаете их несвязанными.
Поиск в ширину обходит граф по уровням, а поиск в глубину идёт по одной ветке до возврата назад.
- BFS использует очередь и находит кратчайшие пути в невзвешенном графе.
- DFS использует стек или рекурсию и подходит для поиска циклов, топологической сортировки и перебора всех путей.
- Оба работают за O(V + E), но память BFS зависит от самого широкого уровня, а память DFS от самого глубокого пути.
Зачем это спрашивают: Интервьюер ищет механизм очередь против стека плюс корректную задачу, где каждый из них подходит лучше.
Quicksort разбивает массив относительно опорного элемента и рекурсивно сортирует две полученные части.
- Элементы меньше опорного перемещаются влево, а элементы больше него вправо.
- Удачные опорные элементы делят массив примерно поровну и дают среднюю сложность O(n log n).
- Постоянный выбор крайнего элемента приводит к O(n^2), хотя случайный выбор или медиана из трёх делают это маловероятным на практике.
Зачем это спрашивают: Знание и средней O(n log n), и худшей O(n^2), плюс того, что её вызывает, показывает глубину сверх заучивания одного числа.
Стабильный алгоритм сортировки сохраняет взаимный порядок элементов, равных по ключу сравнения.
- Стабильность важна, когда сортировки последовательно применяются по нескольким ключам.
- После сортировки по дате и стабильной сортировки по имени записи с одинаковыми именами сохраняют порядок дат.
- Сортировка слиянием стабильна, а наивный quicksort нет, поэтому выбор алгоритма влияет на сохранение предыдущего порядка.
Зачем это спрашивают: Сильный ответ приводит сценарий сортировки по нескольким ключам, доказывая, что стабильность является практическим свойством, а не просто мелочью для эрудиции.
Стек хранит краткоживущие данные вызовов, а куча хранит динамически выделенные данные с более гибким временем жизни.
- Стек содержит кадры вызовов, локальные переменные и адреса возврата и автоматически очищается в порядке LIFO.
- Выделение в стеке быстрое, но его размер ограничен, тогда как куча больше, медленнее и подвержена фрагментации и утечкам.
- Данные в куче могут пережить создавшую их функцию, а данные стека обычно привязаны к области видимости и времени вызова.
Зачем это спрашивают: Это проверяет, связываете ли вы обе области со временем жизни и стоимостью выделения, а не путаете их со структурами данных стек и куча.
Я бы проверил палиндром двумя указателями, которые движутся к центру строки с противоположных концов.
- На каждом шаге сравнивается пара символов, а первое несовпадение сразу даёт false.
- Подход работает за O(n) времени и O(1) дополнительной памяти, в отличие от развёрнутой копии с O(n) лишней памяти.
- Если этого требует определение палиндрома, я бы сначала выровнял регистр и исключил неалфавитно-цифровые символы.
Зачем это спрашивают: Именно решение с двумя указателями и O(1) памяти, а не наивный разворот со сравнением, отличает эффективный ответ от рабочего, но расточительного.
Алгоритм черепахи и зайца Флойда обнаруживает цикл в связном списке без дополнительного хранилища.
- Один указатель продвигается на один узел за шаг, а второй на два.
- При наличии цикла быстрый указатель догонит медленный, а достижение null означает отсутствие цикла.
- Алгоритм работает за O(n) времени и O(1) памяти, тогда как множество посещённых узлов потребовало бы O(n) памяти.
Зачем это спрашивают: Условие без дополнительной памяти проверяет ровно одно: назовёте ли вы приём с двумя указателями и его O(1) памяти.
Стек естественно подходит для отмены, потому что последнее действие нужно обратить первым.
- Каждое действие кладёт предыдущее состояние или обратную команду на стек отмены.
- Операция отмены извлекает и применяет верхнюю запись в порядке LIFO.
- Второй стек поддерживает повтор, сохраняя отменённые действия до их нового применения.
Зачем это спрашивают: Интервьюер хочет рассуждение про LIFO, которое связывает порядок отмены со стеком, плюс бонусное понимание второго стека для повтора.
Добавление в динамический массив имеет амортизированную сложность O(1), потому что дорогие расширения происходят всё реже.
- Заполненный массив обычно удваивает ёмкость и копирует все n элементов за O(n).
- Экспоненциальный рост оставляет много дешёвых добавлений за O(1) между расширениями.
- Распределение общей стоимости копирования между всеми добавлениями даёт постоянную среднюю цену операции.
Зачем это спрашивают: Этот вопрос выделяет понимание амортизированного анализа: удвоение делает дорогой шаг достаточно редким, чтобы стоимость усреднилась до константы.
Множество представляет собой неупорядоченную коллекцию уникальных элементов с быстрой проверкой принадлежности.
- Множество на основе хеш-таблицы обычно проверяет наличие за O(1), тогда как списку нужен проход за O(n).
- Множество лучше списка, когда требуется удалить дубликаты или многократно проверять наличие значения.
- Типичные примеры включают удаление повторных ID пользователей и учёт посещённых значений при обходе графа.
Зачем это спрашивают: Сильный ответ выделяет проверку принадлежности за O(1) как причину предпочесть множество, а не только то, что оно убирает дубликаты.
Повторное склеивание строк часто медленно, поскольку неизменяемая строка при каждом добавлении копируется в новое значение.
- В Java и Python такое построение растущей строки в цикле может суммарно стоить O(n^2).
- Изменяемый буфер вроде StringBuilder в Java или список с последующим str.join в Python дёшево накапливает части.
- Однократное создание итоговой строки в конце снижает общую стоимость до O(n).
Зачем это спрашивают: Настоящее понимание проявляется в том, чтобы распознать скрытую O(n^2) от копий неизменяемых строк и назвать O(n) фикс на основе буфера.
Куча является очередью с приоритетом, которая держит наименьший или наибольший элемент в корне.
- Она даёт доступ к крайнему элементу за O(1), а вставку и удаление за O(log n).
- Она эффективно возвращает следующий по приоритету элемент без полной сортировки всех данных.
- Кучу применяют для планирования задач, слияния отсортированных потоков и выбора ближайшего непосещённого узла в алгоритме Дейкстры.
Зачем это спрашивают: Интервьюер проверяет, связываете ли вы операции кучи за O(log n) с задачей повторного извлечения минимума или максимума, а не определяете её в отрыве от контекста.
Закрытые вопросы
- 21
Каковы четыре столпа объектно-ориентированного программирования?
oop - 22
В чём разница между классом и объектом?
oop - 23
В чём разница между наследованием и композицией, и почему композицию часто предпочитают?
oopownership - 24
Что такое полиморфизм? Приведите конкретный пример.
oop - 25
Что такое инкапсуляция и какие проблемы она предотвращает?
oop - 26
В чём разница между абстрактным классом и интерфейсом?
types - 27
В чём разница между перегрузкой и переопределением метода?
oop - 28
Назовите принципы SOLID и подробно объясните один из них.
solid - 29
Что такое внедрение зависимостей и почему это полезно?
injectiondependencies - 30
Что такое статический метод и когда его уместно использовать?
oop - 31
Каковы ключевые различия между объектно-ориентированным и функциональным программированием?
oopfunctional - 32
Что такое чистая функция и почему чистые функции проще тестировать?
functional - 33
В чём разница между компилируемыми и интерпретируемыми языками?
fundamentals - 34
В чём разница между статической и динамической типизацией и каковы компромиссы?
types - 35
Как в общих чертах работает сборка мусора?
gc - 36
Что такое утечка памяти и может ли она возникнуть в языке со сборкой мусора?
memorygc - 37
Как работает обработка исключений и какие есть хорошие практики использования try/catch?
error-handling - 38
В чём разница между передачей по значению и передачей по ссылке?
fundamentals - 39
Почему null-значения частый источник багов и как от них защищаться?
fundamentals - 40
В чём разница между изменяемыми и неизменяемыми данными и почему неизменяемость помогает?
immutability - 41
Что такое область видимости переменной и почему глобальные переменные не приветствуются?
scope - 42
Что такое состояние гонки и в каких ситуациях с ним может столкнуться джуниор?
concurrency - 43
Зачем команды используют систему контроля версий и какие проблемы она решает?
git - 44
В чём разница между git merge и git rebase?
git - 45
Что такое конфликт слияния и как его безопасно разрешить?
git - 46
Опишите работу с feature-веткой от начала задачи до её слияния.
git - 47
Что на самом деле делает git pull под капотом?
git - 48
Как отменить уже закоммиченное изменение и когда использовать revert вместо reset?
git - 49
Что делает коммит хорошим с точки зрения и размера, и сообщения?
git - 50
Для чего нужен .gitignore и какие файлы никогда нельзя коммитить?
git - 51
В чём разница между юнит-тестами, интеграционными и сквозными (end-to-end) тестами?
e2e - 52
Какие свойства делают юнит-тест хорошим?
unit - 53
В чём разница между моком и стабом и когда их использовать?
mocking - 54
Что такое разработка через тестирование (TDD) и как выглядит цикл красный-зелёный-рефакторинг?
refactoring - 55
Что такое покрытие кода и почему 100 процентов покрытия не гарантирует качество?
coverage - 56
Вы тестируете функцию, которая делит два числа. Какие тест-кейсы вы напишете?
test-cases - 57
Почему при исправлении бага стоит написать тест?
- 58
Что такое флаки-тест и как с ним справляться?
flaky - 59
Опишите ваш порядок действий, когда код бросает ошибку, которую вы раньше не видели.
concurrency - 60
Когда отладчик эффективнее print-ов, а когда print-ы вполне подходят?
debugging - 61
Как отлаживать проблему, которая воспроизводится в продакшене, но не на вашей машине?
debugging - 62
Что такое стек-трейс и как читать его, чтобы найти источник ошибки?
debugging - 63
Вчера код работал, а сегодня сломан. Как найти, какое изменение его сломало?
debugging - 64
Пользователь сообщает о баге, который вы не можете воспроизвести. Каковы ваши дальнейшие шаги?
debugging - 65
Что такое первичный ключ и что делает его хорошим?
primary-keys - 66
В чём разница между INNER JOIN и LEFT JOIN?
joins - 67
Для чего нужны агрегатные функции и GROUP BY? Приведите пример.
aggregation - 68
Что такое нормализация базы данных и чем она полезна?
databasenormalization - 69
Что такое транзакция и что гарантируют свойства ACID?
transactionsacid - 70
Что такое SQL-инъекция и как её предотвратить?
sqlinjection - 71
В чём разница между WHERE и HAVING?
queriesaggregation - 72
Когда вы добавите индекс к таблице и во что обходится индекс?
indexes - 73
Почему условие column = NULL в SQL никогда ничему не соответствует и что использовать вместо него?
sqlschemafundamentals - 74
Как найти дублирующиеся строки в таблице с помощью SQL?
dedupsql - 75
Что происходит, когда вы вводите URL в браузер и нажимаете Enter?
http - 76
Какие HTTP-методы встречаются чаще всего и какие из них идемпотентны?
httpidempotency - 77
Что означают классы HTTP-статусов 2xx, 3xx, 4xx и 5xx?
httpstatus-codes - 78
В чём разница между HTTP и HTTPS?
httphttps - 79
Что такое API и что делает API RESTful?
rest - 80
Почему JSON стал форматом обмена данными по умолчанию для веб-API?
api - 81
Что такое куки и для чего их обычно используют?
cookies - 82
Что такое DNS и что происходит во время DNS-запроса?
dns - 83
Ваш код работает локально, но падает в CI. Как вы будете разбираться?
debugging - 84
Вы попадаете в большую незнакомую кодовую базу, и вам нужно добавить функциональность. Как вы подойдёте к задаче?
learningjoins - 85
На что вы смотрите, когда ревьюите чужой pull request?
code-review - 86
Функция в вашем сервисе работает медленно. Как вы выясните причину, прежде чем оптимизировать?
optimization - 87
Вам нужна функциональность, которую даёт сторонняя библиотека. Как вы решаете между библиотекой и своей реализацией?
decision-makingdependencies - 88
Почему важны имена и как вы подбираете хорошие имена для переменных и функций?
clean-code - 89
Что вы проверяете перед тем, как отправить код на ревью?
code-review - 90
Вы получили задачу с неясными требованиями. Что вы сделаете перед тем, как писать код?
requirements - 91
Расскажите о сложном баге, который вы починили. Как вы его выследили и чему научились?
story - 92
Как вы реагируете, когда код-ревью указывает на серьёзные проблемы в вашей работе?
code-reviewreact - 93
Вы застряли на задаче на несколько часов. Когда и как вы просите о помощи?
problem-solving - 94
Как вы расставляете приоритеты, когда несколько задач кажутся срочными?
prioritization - 95
Расскажите о случае, когда вы что-то сломали. Как вы с этим справились?
story - 96
Как вы подходите к изучению технологии, которую никогда не использовали?
learning - 97
Как вы оцениваете задачу, которую никогда раньше не делали?
estimation - 98
Вы не согласны с техническим подходом коллеги. Что вы делаете?
conflict - 99
Что значит, что задача сделана, помимо того что код компилируется?
definition-of-done - 100
Как вы объясняете техническую проблему нетехническому человеку?
communication