ENIGMA AI
ENIGMA AI
Собеседование

Собеседование Java: гайд, вопросы и задачи с решением 2026

E... Enigma Team 15 мин чтения 7 просмотров
Собеседование Java: гайд, вопросы и задачи с решением 2026
ПОДГОТОВКА · BACKEND · JAVA
На каждом собеседовании повторяются одни и те же темы. Вопрос — какие.

Разбор тысяч технических секций: какие блоки решают исход, реальные вопросы и задачи с решением — для junior, middle и senior.

≈70%вопросов — 4 темы
9 000+разобранных сессий
5задач с решением

// обновлено 06.2026 · источник: агрегированные сессии ENIGMA

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

Материал собран по данным разборов собеседований и подойдёт тем, кто готовится на позицию Java-разработчика любого грейда. Готовые списки реальных вопросов по каждой теме лежат в банке вопросов по backend Java — там собраны топовые запросы из практики ENIGMA.

01 · ФОРМАТ

Как устроено техническое собеседование Java

Большинство процессов в найме Java-разработчика проходят по одному скелету. Понимание этапов снимает половину тревоги: вы заранее знаете, где будут проверять теорию, а где — руки.

  1. Скрининг (HR / рекрутер). Мотивация, опыт, ожидания по деньгам и формату. Техники почти нет.
  2. Техническая секция. Ядро процесса: вопросы по Java Core, коллекциям, многопоточности, JVM и (для backend) по Spring. Здесь решается «проходите дальше или нет».
  3. Лайвкодинг. Задача в реальном времени: строки, коллекции, иногда структуры данных или многопоточность. Оценивают не только итог, но и то, как вы рассуждаете и проверяете решение.
  4. System design. В основном для middle+ и senior: спроектировать сервис, обсудить хранилище, очереди, масштабирование, trade-offs.
  5. Финал. Обсуждение проектов, культурный мэтч, вопросы команде.

На технической секции интервьюер проверяет три вещи: знаете ли вы базу, понимаете ли, как это работает внутри, и умеете ли применять. Ответ «знаю, что есть HashMap» закрывает первый уровень; «знаю, как он устроен и что будет при коллизии» — второй; «выберу нужную коллекцию под задачу и объясню почему» — третий. Именно третий отделяет middle от junior.

// ПРИЁМ Проговаривайте вслух. На лайвкодинге молчание читается как «не знаю». Озвучивайте план до кода: какие случаи учитываете, какую сложность ждёте, где границы. Даже неидеальное решение с внятным разбором сильнее идеального молчания.
02 · ГРЕЙДЫ

Junior, middle, senior: чем отличаются вопросы

Одна и та же тема звучит по-разному в зависимости от грейда. Junior спрашивают «что это», middle — «как работает внутри», senior — «как бы вы это спроектировали и какой ценой». С ростом грейда доля чистой теории падает, а доля архитектуры и обсуждения компромиссов растёт.

Из чего состоит собеседование по грейдам

Условное распределение времени секции: теория · практические задачи · дизайн и архитектура

JUNIORтеория 62% · задачи 33% · дизайн 5%
MIDDLEтеория 42% · задачи 38% · дизайн 20%
SENIORтеория 26% · задачи 31% · дизайн 43%
Теория Задачи / лайвкодинг Дизайн и архитектура
// что проверяют на каждом грейде
ГрейдЧто проверяютТипичные вопросы
JuniorСинтаксис, ООП, базовые коллекции, исключения, основы SQLРазница ArrayList и LinkedList; что такое инкапсуляция; checked vs unchecked
MiddleВнутреннее устройство, многопоточность, Spring, базы данныхКак работает HashMap; volatile vs synchronized; поведение @Transactional
SeniorАрхитектура, trade-offs, производительность, дизайн системКак спроектировать сервис X; выбор и тюнинг GC; согласованность данных
03 · КАРТА ТЕМ

Что и насколько часто спрашивают

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

Доля тем в вопросах на собеседовании Java

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

Java Core (ООП, equals/hashCode, String)24%
Коллекции18%
Многопоточность15%
Spring / Spring Boot13%
JVM и память12%
Базы данных (SQL / JPA)8%
Исключения6%
Прочее (Generics, IO…)4%

Как часто тема всплывает на секции

Доля собеседований, где тема встречается хотя бы одним вопросом

Коллекции91%
Java Core88%
Многопоточность74%
Исключения69%
JVM / память / GC63%
Spring (для backend)58%
SQL / базы данных55%
Generics41%

Вывод для подготовки: коллекции и Java Core пропускать нельзя — они есть почти на каждом собеседовании. Многопоточность и исключения тоже встречаются у большинства. Это не значит, что остальное не важно, но порядок приоритетов очевиден.

// БАНК ВОПРОСОВ

Реальные вопросы по каждой теме

Топовые запросы из практики ENIGMA, сгруппированные по блокам — с формулировками, как их задают на секции.

Смотреть вопросы по Java →
04 · JAVA CORE

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.
  • == vs equals() — для примитивов сравнивает значения, для объектов — ссылки.
  • Абстрактный класс vs интерфейс — когда нужно общее состояние, берут абстрактный класс; когда контракт без состояния — интерфейс (с Java 8 у интерфейсов есть default-методы).
// СВЯЗКА Java Core и коллекции почти всегда идут вместе: за вопросом про equals/hashCode логично следует «а что будет, если положить такой объект в HashMap?». Готовьте их одним блоком. Подборка формулировок — в разделе по Java Core.
05 · КОЛЛЕКЦИИ

Коллекции — самая частая тема

Встречается почти на каждом собеседовании. Минимум, который должен отлетать от зубов: иерархия Collection и Map, разница реализаций и устройство HashMap.

ArrayList vs LinkedList

Вопрос-маркер. Слабый ответ — «LinkedList быстрее на вставку». Сильный — с оговоркой про реальность: вставка в середину у обоих требует поиска позиции, а кэш-локальность массива делает ArrayList быстрее почти всегда на практике.

// сложность операций
ОперацияArrayListLinkedList
Доступ по индексу 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 в горячем пути.

06 · МНОГОПОТОЧНОСТЬ

Многопоточность и 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 — до её чтения. Без этих гарантий компилятор и процессор вправе переупорядочивать операции, и поток может увидеть устаревшее значение.

Что ещё спрашивают

  • Thread vs Runnable — почему предпочитают реализовать Runnable, а не наследовать Thread.
  • ExecutorService и пулы — почему не создают потоки руками; как подобрать размер пула.
  • АтомикиAtomicInteger и CAS как безблокировочная альтернатива synchronized (см. задачу 3).
  • Deadlock — четыре условия и как их разорвать (например, единый порядок захвата блокировок).
  • CompletableFuture — композиция асинхронных операций.
// CONCURRENCY

Вопросы по многопоточности с ответами

Отдельная подборка с формулировками про volatile, happens-before, пулы и взаимоблокировки.

Открыть подборку →
07 · JVM

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 работает, но почти ничего не освобождает. Понимание разницы показывает, что вы умеете диагностировать, а не только воспроизводить термин.

08 · SPRING

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.

09 · ИСКЛЮЧЕНИЯ

Исключения

Короткий, но почти гарантированный блок. Базовое различие: checked-исключения (наследники Exception, кроме RuntimeException) компилятор заставляет обрабатывать или объявлять; unchecked (RuntimeException и наследники) — нет, они для ошибок программирования вроде NullPointerException.

Что спрашивают помимо определения: иерархию Throwable → Error / Exception (и почему Error не ловят); try-with-resources и интерфейс AutoCloseable для гарантированного закрытия ресурсов; почему нельзя «глотать» исключения пустым catch и почему не стоит ловить Exception или Throwable широким хватом без причины.

// ПРИНЦИП Сильный ответ про исключения — про дизайн, а не синтаксис: исключения сигнализируют о нарушении контракта, а не управляют обычным потоком выполнения. Ловить нужно то, что вы реально можете обработать.
10 · ЗАДАЧИ

Практические задачи с решением

Пять задач, которые реально дают на лайвкодинге Java-разработчику. Для каждой — постановка, идея решения и чистый код. Сначала попробуйте сами, потом сверьтесь.

ЗАДАЧА 01

Объект «теряется» в HashMap

JUNIORMIDDLE

Почему второй get() возвращает null, хотя ключ «такой же»?

Problem.java
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 ищет в другом бакете и не находит запись.

Решение

User.java
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 лучше делать иммутабельными, иначе изменение поля после вставки снова «спрячет» элемент.

ЗАДАЧА 02

Средняя зарплата по отделам через Stream API

MIDDLE

Сгруппировать сотрудников по отделу и посчитать среднюю зарплату в каждом.

Problem.java
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, который агрегирует значения внутри каждой группы.

Решение

Solution.java
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().

ЗАДАЧА 03

Потерянные инкременты: гонка данных

MIDDLESENIOR

Восемь потоков по 100 000 инкрементов общего счётчика. Ожидаем 800 000, но регулярно получаем меньше. Почему и как починить?

Problem.java
class Counter {
    private int value = 0;
    void increment() { value++; }   // не атомарно!
    int get() { return value; }
}
// value++ — это три операции: чтение, +1, запись.
// Потоки перетирают результаты друг друга → часть инкрементов теряется.

Разбор. Это гонка данных. value++ не атомарен, а без синхронизации нет ни взаимного исключения, ни видимости. volatile здесь не спасёт — он чинит видимость, но не атомарность. Нужна атомарная операция.

Решение

Solution.java
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, но атомик в таком сценарии быстрее за счёт отсутствия блокировки.

ЗАДАЧА 04

Первый неповторяющийся символ

JUNIORMIDDLE

Вернуть первый символ строки, который встречается ровно один раз. Для "swiss" это 'w'; для "aabb" такого символа нет.

Разбор. Нужны два прохода: сначала посчитать частоты, затем найти первый символ с частотой 1. Чтобы «первый» означало порядок появления в строке, частоты складываем в LinkedHashMap — он хранит порядок вставки.

Решение

Solution.java
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.

ЗАДАЧА 05

LRU-кэш на LinkedHashMap

MIDDLESENIOR

Реализовать кэш фиксированного размера: при переполнении вытесняется самый давно использованный элемент. Доступ и вставка — за O(1).

Разбор. Можно собрать вручную из HashMap и двусвязного списка, но на собеседовании ценят знание стандартной библиотеки. LinkedHashMap в режиме access order сам перемещает использованный элемент в конец, а переопределение removeEldestEntry включает автоматическое вытеснение.

Решение

LruCache.java
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 — чтобы тренироваться на том, что спрашивают.

Перейти к подборке →
11 · ЧЕК-ЛИСТ

Чек-лист подготовки к собеседованию Java

Не пытайтесь выучить всё — двигайтесь по приоритету частоты тем. Рабочий порядок:

  1. Закройте Java Core и коллекции. Они почти на каждом собеседовании. equals/hashCode, String, устройство HashMap, ArrayList vs LinkedList — обязательный минимум.
  2. Разберите многопоточность до уровня модели. Видимость, атомарность, volatile vs synchronized, happens-before, пулы потоков.
  3. Освойте устройство JVM. Память, поколения, базовая логика GC, типы OutOfMemoryError.
  4. Подтяните Spring (для backend): DI, scope бинов, поведение @Transactional.
  5. Решайте задачи вслух. Не «в уме», а проговаривая план, разбор случаев и сложность — именно это оценивают.
  6. Повторяйте по реальным вопросам, а не по случайным спискам. Формулировки и акценты ближе к практике — в банке вопросов по Java.
  7. Подготовьте рассказ о проектах. Технические решения, trade-offs, что бы сделали иначе.
// ПРИНЦИП Глубина важнее ширины. Лучше уверенно владеть топ-5 темами и внятно рассуждать, чем поверхностно знать двадцать. Интервьюер быстро отличает понимание от заученных определений.
12 · FAQ

Частые вопросы о собеседовании 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 — сгруппированы по темам, чтобы тренироваться прицельно.

Открыть банк вопросов по Java →

Похожие статьи

Все статьи