Собеседование Java: гайд, вопросы и задачи с решением 2026
Разбор тысяч технических секций: какие блоки решают исход, реальные вопросы и задачи с решением — для junior, middle и senior.
// обновлено 06.2026 · источник: агрегированные сессии ENIGMA
Собеседование Java выглядит непредсказуемым только снаружи. Если посмотреть на сотни секций сразу, видно главное: набор тем повторяется, а большинство вопросов укладывается в несколько крупных блоков. Дальше — карта этих блоков и практика: разбор частых вопросов и пять задач с решением, которые реально дают на лайвкодинге.
Материал собран по данным разборов собеседований и подойдёт тем, кто готовится на позицию Java-разработчика любого грейда. Готовые списки реальных вопросов по каждой теме лежат в банке вопросов по backend Java — там собраны топовые запросы из практики ENIGMA.
Как устроено техническое собеседование Java
Большинство процессов в найме Java-разработчика проходят по одному скелету. Понимание этапов снимает половину тревоги: вы заранее знаете, где будут проверять теорию, а где — руки.
- Скрининг (HR / рекрутер). Мотивация, опыт, ожидания по деньгам и формату. Техники почти нет.
- Техническая секция. Ядро процесса: вопросы по Java Core, коллекциям, многопоточности, JVM и (для backend) по Spring. Здесь решается «проходите дальше или нет».
- Лайвкодинг. Задача в реальном времени: строки, коллекции, иногда структуры данных или многопоточность. Оценивают не только итог, но и то, как вы рассуждаете и проверяете решение.
- System design. В основном для middle+ и senior: спроектировать сервис, обсудить хранилище, очереди, масштабирование, trade-offs.
- Финал. Обсуждение проектов, культурный мэтч, вопросы команде.
На технической секции интервьюер проверяет три вещи: знаете ли вы базу, понимаете ли, как это работает внутри, и умеете ли применять. Ответ «знаю, что есть HashMap» закрывает первый уровень; «знаю, как он устроен и что будет при коллизии» — второй; «выберу нужную коллекцию под задачу и объясню почему» — третий. Именно третий отделяет middle от junior.
Junior, middle, senior: чем отличаются вопросы
Одна и та же тема звучит по-разному в зависимости от грейда. Junior спрашивают «что это», middle — «как работает внутри», senior — «как бы вы это спроектировали и какой ценой». С ростом грейда доля чистой теории падает, а доля архитектуры и обсуждения компромиссов растёт.
Из чего состоит собеседование по грейдам
Условное распределение времени секции: теория · практические задачи · дизайн и архитектура
| Грейд | Что проверяют | Типичные вопросы |
|---|---|---|
| Junior | Синтаксис, ООП, базовые коллекции, исключения, основы SQL | Разница ArrayList и LinkedList; что такое инкапсуляция; checked vs unchecked |
| Middle | Внутреннее устройство, многопоточность, Spring, базы данных | Как работает HashMap; volatile vs synchronized; поведение @Transactional |
| Senior | Архитектура, trade-offs, производительность, дизайн систем | Как спроектировать сервис X; выбор и тюнинг GC; согласованность данных |
Что и насколько часто спрашивают
Главный вывод из массива собеседований простой: темы распределены крайне неравномерно. Несколько блоков забирают львиную долю вопросов — и именно их пропуск чаще всего стоит оффера. Ниже два среза: доля темы в вопросах и как часто тема вообще всплывает на секции.
Доля тем в вопросах на собеседовании Java
Какую часть всех заданных вопросов занимает каждый блок
Как часто тема всплывает на секции
Доля собеседований, где тема встречается хотя бы одним вопросом
Вывод для подготовки: коллекции и Java Core пропускать нельзя — они есть почти на каждом собеседовании. Многопоточность и исключения тоже встречаются у большинства. Это не значит, что остальное не важно, но порядок приоритетов очевиден.
Реальные вопросы по каждой теме
Топовые запросы из практики ENIGMA, сгруппированные по блокам — с формулировками, как их задают на секции.
Java Core — фундамент любого собеседования
Самый частый блок. Здесь проверяют, понимаете ли вы язык на уровне механики, а не заученных определений. Разберём то, что спрашивают чаще всего.
ООП: не определения, а смысл
Просьба «расскажите про четыре принципа ООП» — это тест на то, отвечаете ли вы шаблоном или по сути. Сильный ответ привязывает каждый принцип к задаче: инкапсуляция прячет состояние за методами, чтобы инвариант нельзя было нарушить снаружи; наследование переиспользует поведение, но создаёт жёсткую связанность; полиморфизм позволяет работать с разными реализациями через общий тип; абстракция выделяет то, что важно для задачи, и прячет остальное. Хороший контрвопрос, к которому стоит готовиться: «почему композицию часто предпочитают наследованию?»
equals() и hashCode(): контракт
Классика, которая ловит даже опытных. Правило: если два объекта равны по equals(), их hashCode() обязан совпадать. Обратное не требуется — разные объекты могут иметь одинаковый хеш (коллизия). Нарушение контракта ломает работу в HashMap и HashSet: объект «теряется». Полный разбор — в первой задаче ниже.
String: иммутабельность, пул и сравнение
String неизменяем — это даёт потокобезопасность, безопасное кеширование хеша и работу строкового пула. Литералы попадают в пул, поэтому == для них может вернуть true, но сравнивать строки нужно через equals(): == проверяет ссылки, а не содержимое. Для конкатенации в цикле — StringBuilder, иначе каждый шаг создаёт новый объект.
Частые «вопросы-ловушки»
final,finally,finalize— три не связанных понятия: модификатор неизменности, блок гарантированного выполнения и (устаревший) метод перед сборкой мусора.- Кеш
Integer— значения от −128 до 127 кешируются, поэтомуInteger a = 100; Integer b = 100; a == bдаётtrue, а для 1000 — ужеfalse. ==vsequals()— для примитивов сравнивает значения, для объектов — ссылки.- Абстрактный класс vs интерфейс — когда нужно общее состояние, берут абстрактный класс; когда контракт без состояния — интерфейс (с Java 8 у интерфейсов есть default-методы).
equals/hashCode логично следует «а что будет, если положить такой объект в HashMap?». Готовьте их одним блоком. Подборка формулировок — в разделе по Java Core.
Коллекции — самая частая тема
Встречается почти на каждом собеседовании. Минимум, который должен отлетать от зубов: иерархия Collection и Map, разница реализаций и устройство HashMap.
ArrayList vs LinkedList
Вопрос-маркер. Слабый ответ — «LinkedList быстрее на вставку». Сильный — с оговоркой про реальность: вставка в середину у обоих требует поиска позиции, а кэш-локальность массива делает ArrayList быстрее почти всегда на практике.
| Операция | ArrayList | LinkedList |
|---|---|---|
Доступ по индексу get(i) | O(1) | O(n) |
| Вставка/удаление в конец | O(1)* | O(1) |
| Вставка/удаление в середину | O(n) | O(n)** |
| Память на элемент | меньше | больше (ссылки) |
| Кэш-локальность | высокая | низкая |
* амортизированно, при расширении массива происходит копирование. ** O(1) при наличии ссылки на узел, но поиск самого узла — O(n).
Как устроен HashMap внутри
Любимый вопрос на middle. Под капотом — массив «корзин» (бакетов). Индекс бакета вычисляется из hashCode() ключа. При коллизии (несколько ключей в один бакет) элементы складываются в связный список, а начиная с Java 8, если список в бакете становится длинным (порог — 8 элементов) и таблица достаточно велика, он превращается в сбалансированное дерево — это улучшает худший случай с O(n) до O(log n). При заполнении сверх порога (load factor, по умолчанию 0.75) таблица увеличивается и происходит рехеширование.
Отсюда вытекает связь с предыдущим блоком: без корректных equals() и hashCode() у ключа HashMap работает неправильно. Плохой hashCode(), возвращающий константу, превращает мапу в один длинный список и убивает производительность.
Fail-fast и потокобезопасность
Итераторы большинства коллекций — fail-fast: если изменить коллекцию во время обхода (не через сам итератор), вы получите ConcurrentModificationException. Это защита от незаметных багов, а не потокобезопасность. Для конкурентного доступа берут ConcurrentHashMap или CopyOnWriteArrayList, а не Collections.synchronizedMap в горячем пути.
Многопоточность и concurrency
Тема, которая отделяет middle от junior. Здесь важно не количество выученных классов, а понимание модели: видимость, атомарность, гонки и взаимоблокировки.
volatile vs synchronized
Частая пара. volatile гарантирует видимость: запись в переменную сразу видна другим потокам, но не делает операцию атомарной. synchronized даёт и взаимное исключение, и видимость, но ценой блокировки. Поэтому volatile boolean flag для флага остановки — ок, а volatile int counter для counter++ — нет: инкремент не атомарен.
happens-before
Ключевая идея модели памяти Java: если действие A происходит-до (happens-before) действия B, то результат A гарантированно виден в B. Выход из synchronized-блока происходит-до входа в него же другим потоком; запись в volatile — до её чтения. Без этих гарантий компилятор и процессор вправе переупорядочивать операции, и поток может увидеть устаревшее значение.
Что ещё спрашивают
ThreadvsRunnable— почему предпочитают реализоватьRunnable, а не наследоватьThread.ExecutorServiceи пулы — почему не создают потоки руками; как подобрать размер пула.- Атомики —
AtomicIntegerи CAS как безблокировочная альтернативаsynchronized(см. задачу 3). - Deadlock — четыре условия и как их разорвать (например, единый порядок захвата блокировок).
CompletableFuture— композиция асинхронных операций.
Вопросы по многопоточности с ответами
Отдельная подборка с формулировками про volatile, happens-before, пулы и взаимоблокировки.
JVM, память и сборка мусора
На middle и senior проверяют, понимаете ли вы, что происходит «под» вашим кодом. Знать наизусть параметры тюнинга не обязательно — обязательно понимать модель.
Память: heap, stack, metaspace
Heap — общая куча для объектов, делится на молодое поколение (Eden + Survivor) и старое. Stack — свой у каждого потока, хранит фреймы вызовов и локальные переменные; его переполнение даёт StackOverflowError (например, бесконечная рекурсия). Metaspace — метаданные классов; с Java 8 заменил PermGen и вынесен из кучи в нативную память.
Сборка мусора
GC автоматически освобождает объекты, на которые нет достижимых ссылок. Базовая идея — большинство объектов умирают молодыми, поэтому young-коллекции частые и быстрые, а old — реже и дороже. С Java 9 сборщик по умолчанию — G1; для задач с жёсткими требованиями к паузам существуют ZGC и Shenandoah с паузами в доли миллисекунды. Хороший ответ на «какой GC выбрать» — это вопрос про trade-off между пропускной способностью и латентностью.
OutOfMemoryError — это не всегда «мало памяти»
Полезно различать типы: Java heap space — кончилась куча (часто утечка через коллекцию, которая растёт без удаления); Metaspace — слишком много загруженных классов; GC overhead limit exceeded — GC работает, но почти ничего не освобождает. Понимание разницы показывает, что вы умеете диагностировать, а не только воспроизводить термин.
Spring и Spring Boot
Для backend-вакансий — почти обязательный блок. У junior достаточно понимать идею DI; у middle и senior спрашивают предметно.
IoC и Dependency Injection
Inversion of Control — управление зависимостями отдаётся контейнеру; DI — конкретный способ это сделать. Предпочтительная форма — внедрение через конструктор: зависимости становятся обязательными и неизменяемыми, объект нельзя создать в невалидном состоянии, и это упрощает тестирование.
Scope бинов
По умолчанию бин — singleton: один экземпляр на контейнер. Есть prototype (новый на каждый запрос), а в веб-контексте — request и session. Классическая ловушка: внедрение prototype-бина в singleton не создаёт новый экземпляр на каждый вызов — singleton получает зависимость один раз при создании.
@Transactional: где спотыкаются
Любимая тема на собеседовании, потому что вокруг неё много неочевидного:
- Self-invocation. Вызов
@Transactional-метода из другого метода того же класса идёт мимо прокси — транзакция не стартует. Spring оборачивает бин прокси, а внутренний вызов его не проходит. - Правила отката. По умолчанию откат происходит на unchecked-исключениях (
RuntimeException), но не на checked. Чтобы откатываться и на проверяемых, нужно явно указатьrollbackFor. - Propagation.
REQUIREDприсоединяется к текущей транзакции,REQUIRES_NEWвсегда открывает новую — это меняет поведение при вложенных вызовах.
Ещё частое: разница @Component и @Bean (сканирование класса против явного метода-фабрики), а также жизненный цикл бина с хуками @PostConstruct и @PreDestroy.
Исключения
Короткий, но почти гарантированный блок. Базовое различие: checked-исключения (наследники Exception, кроме RuntimeException) компилятор заставляет обрабатывать или объявлять; unchecked (RuntimeException и наследники) — нет, они для ошибок программирования вроде NullPointerException.
Что спрашивают помимо определения: иерархию Throwable → Error / Exception (и почему Error не ловят); try-with-resources и интерфейс AutoCloseable для гарантированного закрытия ресурсов; почему нельзя «глотать» исключения пустым catch и почему не стоит ловить Exception или Throwable широким хватом без причины.
Практические задачи с решением
Пять задач, которые реально дают на лайвкодинге Java-разработчику. Для каждой — постановка, идея решения и чистый код. Сначала попробуйте сами, потом сверьтесь.
Объект «теряется» в HashMap
JUNIORMIDDLEПочему второй get() возвращает null, хотя ключ «такой же»?
Map<User, String> roles = new HashMap<>();
roles.put(new User(1, "Андрей"), "admin");
String role = roles.get(new User(1, "Андрей"));
System.out.println(role); // null — почему?
Разбор. Класс User не переопределяет equals() и hashCode(), поэтому используется реализация по умолчанию — сравнение по ссылке. Два разных объекта с одинаковыми полями считаются разными: их хеши отличаются, HashMap ищет в другом бакете и не находит запись.
Решение
public final class User {
private final int id;
private final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
Теперь объекты с одинаковыми полями равны и дают одинаковый хеш — запись находится. Ключи в HashMap лучше делать иммутабельными, иначе изменение поля после вставки снова «спрячет» элемент.
Средняя зарплата по отделам через Stream API
MIDDLEСгруппировать сотрудников по отделу и посчитать среднюю зарплату в каждом.
record Employee(String name, String dept, double salary) {}
List<Employee> staff = List.of(
new Employee("Анна", "backend", 280_000),
new Employee("Игорь", "backend", 240_000),
new Employee("Лена", "frontend", 230_000),
new Employee("Пётр", "frontend", 210_000),
new Employee("Олег", "devops", 300_000)
);
Разбор. Задача проверяет знание коллекторов. Нужна связка groupingBy (ключ — отдел) и downstream-коллектор averagingDouble, который агрегирует значения внутри каждой группы.
Решение
Map<String, Double> avgByDept = staff.stream()
.collect(Collectors.groupingBy(
Employee::dept,
Collectors.averagingDouble(Employee::salary)
));
// {backend=260000.0, frontend=220000.0, devops=300000.0}
Частый follow-up: «а как найти отдел с максимальной средней зарплатой?» — обернуть результат в ещё один stream и взять max по значению через Map.Entry.comparingByValue().
Потерянные инкременты: гонка данных
MIDDLESENIORВосемь потоков по 100 000 инкрементов общего счётчика. Ожидаем 800 000, но регулярно получаем меньше. Почему и как починить?
class Counter {
private int value = 0;
void increment() { value++; } // не атомарно!
int get() { return value; }
}
// value++ — это три операции: чтение, +1, запись.
// Потоки перетирают результаты друг друга → часть инкрементов теряется.
Разбор. Это гонка данных. value++ не атомарен, а без синхронизации нет ни взаимного исключения, ни видимости. volatile здесь не спасёт — он чинит видимость, но не атомарность. Нужна атомарная операция.
Решение
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private final AtomicInteger value = new AtomicInteger(0);
void increment() { value.incrementAndGet(); }
int get() { return value.get(); }
}
AtomicInteger использует CAS (compare-and-swap) — безблокировочную атомарную операцию на уровне процессора. Альтернатива — пометить increment() словом synchronized, но атомик в таком сценарии быстрее за счёт отсутствия блокировки.
Первый неповторяющийся символ
JUNIORMIDDLEВернуть первый символ строки, который встречается ровно один раз. Для "swiss" это 'w'; для "aabb" такого символа нет.
Разбор. Нужны два прохода: сначала посчитать частоты, затем найти первый символ с частотой 1. Чтобы «первый» означало порядок появления в строке, частоты складываем в LinkedHashMap — он хранит порядок вставки.
Решение
static Character firstUnique(String s) {
Map<Character, Integer> counts = new LinkedHashMap<>();
for (char c : s.toCharArray()) {
counts.merge(c, 1, Integer::sum);
}
for (Map.Entry<Character, Integer> e : counts.entrySet()) {
if (e.getValue() == 1) return e.getKey();
}
return null;
}
// Время: O(n) · Память: O(k), где k — размер алфавита
Метод merge — компактный способ инкрементировать счётчик: кладёт 1, если ключа нет, иначе применяет Integer::sum. На собеседовании это плюс к ответу — знание удобных методов Map.
LRU-кэш на LinkedHashMap
MIDDLESENIORРеализовать кэш фиксированного размера: при переполнении вытесняется самый давно использованный элемент. Доступ и вставка — за O(1).
Разбор. Можно собрать вручную из HashMap и двусвязного списка, но на собеседовании ценят знание стандартной библиотеки. LinkedHashMap в режиме access order сам перемещает использованный элемент в конец, а переопределение removeEldestEntry включает автоматическое вытеснение.
Решение
class LruCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
LruCache(int capacity) {
super(capacity, 0.75f, true); // accessOrder = true
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
}
Третий аргумент конструктора true — это и есть access order: при каждом get() элемент уходит в «свежий» конец. Когда размер превышает ёмкость, removeEldestEntry возвращает true и самый старый элемент удаляется автоматически.
Больше задач и вопросов с собеседований
Подборка реальных формулировок по Java Core, коллекциям, многопоточности и Spring — чтобы тренироваться на том, что спрашивают.
Чек-лист подготовки к собеседованию Java
Не пытайтесь выучить всё — двигайтесь по приоритету частоты тем. Рабочий порядок:
- Закройте Java Core и коллекции. Они почти на каждом собеседовании.
equals/hashCode,String, устройствоHashMap,ArrayListvsLinkedList— обязательный минимум. - Разберите многопоточность до уровня модели. Видимость, атомарность,
volatilevssynchronized, happens-before, пулы потоков. - Освойте устройство JVM. Память, поколения, базовая логика GC, типы
OutOfMemoryError. - Подтяните Spring (для backend): DI, scope бинов, поведение
@Transactional. - Решайте задачи вслух. Не «в уме», а проговаривая план, разбор случаев и сложность — именно это оценивают.
- Повторяйте по реальным вопросам, а не по случайным спискам. Формулировки и акценты ближе к практике — в банке вопросов по Java.
- Подготовьте рассказ о проектах. Технические решения, trade-offs, что бы сделали иначе.
Частые вопросы о собеседовании Java
Какие темы чаще всего спрашивают на собеседовании Java?
Чаще всего — Java Core (ООП, equals/hashCode, String, иммутабельность), коллекции (ArrayList, устройство HashMap), многопоточность (synchronized, volatile, пулы потоков), устройство JVM и сборка мусора, а для backend — Spring. На эти блоки приходится большинство вопросов.
Сколько времени готовиться к собеседованию Java?
При наличии базы — 2–4 недели системной подготовки по топовым темам. С нуля до уверенного junior — несколько месяцев. Важнее не срок, а покрытие частых тем и решение задач вслух, как на реальной секции.
Что спрашивают джуна на собеседовании Java?
У junior проверяют синтаксис и ООП, базовые коллекции (разница ArrayList и LinkedList), исключения, equals/hashCode, основы SQL и простые задачи на строки и массивы. Глубокого знания JVM и concurrency обычно не требуют.
Нужен ли Spring на собеседовании Java?
Для junior достаточно понимать идею Dependency Injection. Для middle и senior на backend Spring и Spring Boot спрашивают почти всегда: бины и их scope, транзакции и поведение @Transactional, жизненный цикл бина.
Дают ли задачи на собеседовании Java?
Да. Кроме теории почти всегда есть лайвкодинг: задачи на строки и коллекции, equals/hashCode, Stream API, многопоточность, иногда структуры данных вроде LRU-кэша. Оценивают не только результат, но и ход мысли.
Коротко
Собеседование Java перестаёт быть лотереей, когда вы видите его структуру: несколько тем решают исход, и их стоит закрыть в первую очередь. Понимание механики вместо заученных определений, привычка рассуждать вслух и практика на реальных вопросах дают предсказуемый результат на любом грейде.
Готовьтесь по реальным вопросам
Топовые запросы по Java из практики ENIGMA — сгруппированы по темам, чтобы тренироваться прицельно.
Похожие статьи
Собеседование в Ozon Tech: этапы, вопросы и как подготовиться
Собеседование в VK: этапы, вопросы и как подготовиться
Собеседование в Авито: этапы, вопросы и как подготовиться