Подготовка к собеседованию — Backend-инженер
Колода из 625+ карточек с вопросами для собеседования по направлению «Backend-инженер» — по темам и уровню сложности, с возвратом ровно перед тем, как вы забудете. Посмотрите несколько карточек ниже, затем выберите доступ, чтобы учить всё направление по расписанию в стиле Anki (SM-2).
7 дней бесплатно на месяц или год · все функции включены.
Что внутри
Все темы направления, сгруппированные так, как вы будете их учить.
Распределённые системы
12 карточекKafka и очереди
12 карточекКэширование вглубь
10 карточекПроизводительность и конкурентность
11 карточекНадёжность и инциденты
10 карточекPython
122 карточкиБазы данных
104 карточкиБэкенд
169 карточекОсновы CS
76 карточекDevOps и инфраструктура
35 карточекСистемный дизайн
29 карточекПоведенческое интервью
35 карточекПримеры вопросов
Несколько карточек из колоды — откройте ответ, затем выберите доступ, чтобы учить весь набор по расписанию.
Event sourcing и CQRS: когда они оправданы и какова цена?
Event sourcing и CQRS: когда они оправданы и какова цена?
Короткий ответ: Event sourcing: состояние не хранится, а выводится — state = fold(events); первичен журнал событий. CQRS: модель записи и модели чтения разделены. Дают полный аудит, temporal queries («как выглядел заказ вчера»), пересборку проекций с нуля. Цена высокая: версионирование событий, снапшоты, eventually consistent read-модели, дорогой тулинг. Это не архитектура по умолчанию.
Подробно:
events: OrderCreated → ItemAdded → ItemAdded → OrderPaid
state = fold(events) — всегда выводимо заново
проекции: «заказы за день», «топ товаров» — свои read-модели,
можно пересобрать из журнала с нуля
- Когда оправдано — движение денег, леджеры, домены с жёстким аудитом и комплаенсом; «почему баланс стал таким» — вопрос бизнеса, а не логов.
- Цена №1: версионирование — событие живёт вечно; изменилась схема — читать все старые версии (upcasters) или мигрировать журнал целиком.
- Цена №2: чтение — read-модели асинхронны: после команды проекция отстаёт, и UI, тесты, саппорт обязаны это переживать.
- CQRS без ES — легитимен и сильно дешевле: обычная БД записи + денормализованные проекции для чтения.
⚠️ Частая ошибка: предлагать event sourcing для CRUD-приложения «на вырост». Если аудит — не бизнес-требование, вы платите всю цену ES и не получаете ничего, что не дала бы таблица плюс журнал изменений.
Как консюмер-группа распределяет партиции и что будет, если консюмеров больше, чем партиций?
Как консюмер-группа распределяет партиции и что будет, если консюмеров больше, чем партиций?
Короткий ответ: Внутри группы каждая партиция читается максимум одним консюмером. Консюмеров больше, чем партиций — лишние простаивают: максимальный параллелизм группы равен числу партиций. Разные группы читают один топик независимо, у каждой свои оффсеты.
Подробно:
topic orders: p0 p1 p2 p3
group A (3 консюмера):
c1 ◄─ p0, p1 c2 ◄─ p2 c3 ◄─ p3
group A (6 консюмеров):
c1◄p0 c2◄p1 c3◄p2 c4◄p3 c5, c6 — idle
group B: читает те же партиции независимо (свои оффсеты)
- Внутри группы — партиция достаётся ровно одному консюмеру: так сохраняется порядок обработки внутри партиции.
- Между группами — независимое чтение: каждая группа коммитит свои оффсеты в
__consumer_offsets, одни и те же данные обслуживают и биллинг, и аналитику. - Планирование — число партиций задаёт потолок масштабирования группы; закладывайте его с запасом при создании топика.
⚠️ Частая ошибка: «добавим консюмеров — станет быстрее». После числа партиций добавленные консюмеры просто простаивают.
Cache-aside, write-through, write-behind: как работает каждая стратегия и когда какую выбрать?
Cache-aside, write-through, write-behind: как работает каждая стратегия и когда какую выбрать?
Короткий ответ: Cache-aside — приложение читает кэш, на промахе идёт в БД и кладёт результат; на запись пишет в БД и инвалидирует ключ. Write-through — запись синхронно в кэш и в хранилище. Write-behind — ack от кэша, в хранилище сбрасывается асинхронно. Дефолт — cache-aside; остальные две — под конкретные профили нагрузки.
Подробно:
| Cache-aside | Write-through | Write-behind | |
|---|---|---|---|
| Чтение | miss → БД → кэш | из кэша | из кэша |
| Запись | БД + инвалидировать ключ | кэш + БД синхронно | кэш сразу, БД потом |
| Консистентность | окно stale после записи | читатели видят свежее | слабая до сброса |
| Латентность записи | как у БД | БД + кэш: медленнее | только кэш: быстро |
| Риск потери | нет | нет | есть: упал до сброса |
- Cache-aside — кэш не стоит на критическом пути записи и его падение переживается (просто больше промахов); цена — свой протокол инвалидации на каждую запись.
- Write-through — консистентные чтения из коробки; цена — каждая запись ждёт оба хранилища, и кэшируется в том числе то, что никто не прочитает.
- Write-behind — буфер для шквала записей (счётчики, лайки, метрики); без durable-буфера (очередь, AOF) падение узла = потерянные записи.
⚠️ Частая ошибка: выбирать write-behind «для скорости», не ответив, что случится с несброшенными записями при падении процесса.
N+1-запросы: как их заметить и как чинить?
N+1-запросы: как их заметить и как чинить?
Короткий ответ: N+1 — один запрос за списком и ещё по запросу на каждый элемент. Симптом: латентность растёт линейно с размером коллекции, а в логе — серия одинаковых по форме запросов, отличающихся только id. Лечится жадной загрузкой или батчингом по id.
Подробно:
- Заметить — лог SQL (echo, debug toolbar): десятки одинаковых
SELECT … WHERE id = ?; в тестах — ассерт на число запросов (django_assert_num_queries, счётчик на событиях SQLAlchemy). - Починить — жадная загрузка: select_related/prefetch_related в Django, joinedload/selectinload в SQLAlchemy; либо вручную собрать id и сделать один
WHERE id IN (…). - Закрепить — тем же тестом на число запросов: регрессия не проедет в прод молча.
# было: 1 + N запросов
for order in orders:
print(order.customer.name) # запрос на каждой итерации
# стало: JOIN, один запрос
orders = Order.objects.select_related("customer")
⚠️ Частая ошибка: сделать prefetch, а потом в цикле дёрнуть .filter()/.count() по связанной коллекции — ORM снова уходит в БД на каждой итерации, и N+1 возвращается, хотя «префетч же есть».
Таймаут провайдера в середине платежа: деньги списаны или нет? Что делает ваш код?
Таймаут провайдера в середине платежа: деньги списаны или нет? Что делает ваш код?
Короткий ответ: Неизвестно — таймаут значит «ответ не дошёл», а не «операция не выполнилась». Слепой ретрай неидемпотентного списания — прямой путь к двойному списанию. Правильно: запросить статус операции у провайдера / дождаться вебхука, а финальную согласованность даёт сверка (reconciliation) по реестрам провайдера.
Подробно:
t0 наш сервис ──► POST /charge ──► провайдер
t1 провайдер списал деньги ✓
t2 ответ потерялся в сети ✗
t3 у нас таймаут: исход НЕИЗВЕСТЕН (окно t1–t3)
- Три неразличимых исхода — запрос не дошёл; провайдер упал посередине; ответ потерялся на обратном пути. Снаружи они одинаковы.
- Не ретраить вслепую — платёж у нас остаётся в PENDING; опрашиваем статус-API провайдера или ждём вебхук, и только по факту двигаем state machine.
- Идемпотентность у провайдера — передаём провайдеру свой ключ идемпотентности: тогда даже ретрай безопасен by design, дубль он отобьёт сам.
- Сверка — регулярная джоба сравнивает наши подвисшие/итоговые статусы с отчётами провайдера и закрывает расхождения: деньги не теряются молча.
⚠️ Частая ошибка: трактовать таймаут как «не списалось» и пометить платёж FAILED — клиент платит второй раз, а первое списание всплывает потом при сверке.
Чем отличаются изменяемые (mutable) и неизменяемые (immutable) типы?
Чем отличаются изменяемые (mutable) и неизменяемые (immutable) типы?
Короткий ответ: Изменяемые объекты можно менять «на месте» без создания нового объекта (list, dict, set, bytearray), неизменяемые — нет (int, float, str, bytes, tuple, frozenset, bool, None). Любое «изменение» неизменяемого создаёт новый объект.
Подробно: Изменяемость — это про то, может ли объект поменять своё содержимое, сохранив тот же id() (адрес в памяти).
# Неизменяемый: операция создаёт НОВЫЙ объект
s = "hello"
print(id(s))
s += " world" # создаётся новая строка
print(id(s)) # id поменялся — это другой объект
# Изменяемый: меняется тот же объект
lst = [1, 2, 3]
print(id(lst))
lst.append(4) # меняем на месте
print(id(lst)) # id тот же
Почему это важно:
- Хешируемость. Только неизменяемые (по содержимому) объекты можно класть в
dict-ключи иset. Если бы ключ менялся, его хеш «уехал» бы, и его нельзя было бы найти. - Безопасность при шаринге. Неизменяемый объект можно безопасно расшарить между потоками/функциями — никто его не испортит.
- Аргументы функций. Python передаёт ссылку на тот же объект: функция может изменить содержимое изменяемого аргумента, но локальное переназначение параметра не меняет переменную вызывающего кода.
⚠️ Ловушка: tuple неизменяем, но если в нём лежит list, этот список можно поменять:
t = ([1, 2], 3)
t[0].append(99) # OK! список внутри мутабелен
print(t) # ([1, 2, 99], 3)
# t[0] = [...] # вот ЭТО — TypeError, переприсвоить элемент нельзя
А ещё hash(([1,2], 3)) упадёт — кортеж нехешируем, если внутри есть нехешируемый элемент.
Готовы закрепить навсегда?
Первая сессия — меньше минуты. Ваше будущее «я» на собеседовании скажет спасибо.
Вопросы об этом направлении
Как готовиться к собеседованию на «Backend-инженер»?
Учите концепции, которые придётся объяснять, а не только те, что умеете кодить. Направление «Backend-инженер» в RecallDeck даёт 625+ отобранных вопросов и возвращает каждый по расписанию в стиле Anki (SM-2) ровно перед тем, как вы забудете — чтобы на собеседовании ответы были под рукой.
Какие темы охватывает направление «Backend-инженер»?
Направление «Backend-инженер» разбито на ключевые области, которые реально проверяют на таких собеседованиях, — по темам и уровню сложности (Concept, Junior, Middle, Senior). Полный план и примеры вопросов можно посмотреть выше до входа.
Помогает ли интервальное повторение в подготовке к «Backend-инженер»?
Да. Активно вспоминать ответ и честно себя оценивать — куда прочнее для памяти, чем перечитывать заметки. RecallDeck планирует каждую карту «Backend-инженер» так, чтобы она вернулась перед моментом забывания: ежедневных повторений становится меньше, а знания держатся.
Можно попробовать направление «Backend-инженер» до оплаты?
Да. Месячный и годовой варианты включают семь пробных дней со всем направлением «Backend-инженер», полным планировщиком SM-2, статистикой, гибким темпом и блиц-режимом. Отменить можно онлайн до первого списания.