Как проходят собесы на Senior Backend: Go, Python и универсальный роадмап
Мы посмотрели несколько часов контента по техническим собеседованиям на Senior и Middle/Senior Backend позиции — живые мок-интервью и структурированные роадмапы. Охват широкий: Go с его внутренностями рантайма, Python с GIL и PostgreSQL, плюс универсальная карта знаний для любого бэкенд-стека.
Материал будет полезен тем, кто готовится к собесу в ближайшие недели, а не «когда-нибудь потом». Здесь нет воды про «будь уверен в себе» — только конкретные темы, типичные ошибки кандидатов и то, что реально спрашивают на разных уровнях.
Главный вывод, который прослеживается во всех источниках: платят на Senior не за знание синтаксиса, а за понимание того, почему код работает именно так и где он сломается в продакшене.
Что проверяют на Senior Go: внутренности, а не API
Слайсы и map: вопрос не «что», а «почему»
На джуниорском собесе достаточно знать, что append увеличивает слайс. На Senior от тебя ожидают другого уровня разговора.
Slice header — это структура из трёх полей: указатель на массив, длина (len) и ёмкость (cap). При передаче слайса в функцию копируется именно этот заголовок. Отсюда важное следствие: изменения длины внутри функции снаружи не видны, но запись в общий массив — видна всем, кто держит ссылку на тот же backing array. Это классический источник неожиданных мутаций.
При переполнении capacity рантайм выделяет новый массив и копирует данные. Правила роста capacity — не контракт обратной совместимости. Они менялись между версиями Go и могут измениться снова. Рассчитывать на конкретный коэффициент роста в продакшн-коде — плохая идея.
По map — здесь свежий материал. Начиная с Go 1.24 появились SwissTable map. Старая реализация хранила данные в бакетах по 8 значений, коллизии решались через overflow buckets по указателю. Новая использует открытую адресацию.
Как работает поиск в SwissTable? Хеш ключа делится на две части: - H1 — старшие биты, определяют группу из 8 слотов - H2 — младшие 7 бит, записываются в control byte каждого слота как метаданные
Сначала идёт поиск по метаданным всей группы за одну SIMD-инструкцию. Полное сравнение ключа запускается только при совпадении control byte. Это быстрее, чем перебирать бакеты подряд.
SIMD (Single Instruction Multiple Data) — не многоядерный параллелизм. Один такт, один регистр, одна инструкция обрабатывает пачку данных. На x86 это SSE-регистры на 128 бит (16 байт или 4 значения float32). Go-компилятор, в отличие от LLVM, не векторизует циклы автоматически.
Коллизии в хеш-функциях неизбежны — это следствие принципа Дирихле: бесконечное множество входов отображается в конечное множество хеш-значений.
time.Now() и память: детали, которые отличают Senior
time.Now() содержит два компонента: настенные часы (wall clock) и монотонные часы (monotonic clock). Настенные могут идти назад — например, при синхронизации NTP. Монотонные — только вперёд. При вычитании двух значений time.Time Go использует монотонные часы, поэтому разница всегда корректна. Казалось бы, деталь — но именно такие детали вскрываются на реальных собесах.
По управлению памятью: после сборки мусора память не возвращается в ОС немедленно. Она остаётся в аллокаторе рантайма для переиспользования. Обратно в ОС её отдаёт фоновый процесс. Это важно понимать, когда смотришь на RSS процесса и удивляешься, почему он не падает после нагрузочного теста.
Нетипизированные константы в Go не существуют в рантайме вообще. Компилятор вычисляет их на этапе компиляции и подставляет в места использования. Адреса в памяти у них нет.
Escape-анализ определяет, куда попадёт переменная — на стек или в кучу. Стек дешевле: выделение и освобождение за O(1). Куча требует участия GC. Если переменная «убегает» наружу (например, её адрес уходит в функцию или интерфейс), компилятор кладёт её в кучу.
Горутины: классические ловушки на живом коде
На собесах по Go почти всегда есть задача с горутинами, где нужно найти баг. Вот типичный паттерн:
// Проблемный код
func sendAll(senders []Sender) error {
ch := make(chan error)
for _, s := range senders {
go func(s Sender) {
ch <- s.Send()
}(s)
}
for range senders {
if err := <-ch; err != nil {
return err // выходим при первой ошибке
}
}
return nil
}
Проблема: при выходе по первой ошибке оставшиеся горутины пытаются записать в канал, но читателя уже нет. Они висят на ch <- навсегда — утечка горутин. Память не освобождается, процесс тихо распухает.
Корректное решение — errgroup или явная обработка через context с отменой. Важный момент по recover(): он ловит панику только в той же горутине, где вызывается. Паника из одной горутины в другую не всплывает. Паника в горутине без recover кладёт весь процесс целиком.
Python Middle/Senior: GIL, PostgreSQL и Redis в деталях
GIL и параллелизм: честный разговор
GIL (Global Interpreter Lock) — механизм, который не позволяет более чем одному потоку CPython исполняться одновременно. Это означает: для CPU-bound задач (вычисления, обработка данных) многопоточность в Python не даёт реального параллелизма. Потоки переключаются, но не выполняются одновременно.
Для IO-bound задач (сеть, файлы, база) GIL почти не мешает: пока один поток ждёт ответа от сети, другой может работать.
Экспериментальное отключение GIL появилось в Python 3.14, но до продакшн-использования ещё далеко. Пока реальный параллелизм в Python достигается через multiprocessing (отдельные процессы) или asyncio (кооперативная многозадачность для IO).
Go в этом отношении принципиально другой: горутины не имеют аналога GIL, их можно запускать тысячами, планировщик Go распределяет их по нескольким OS-потокам.
Контекст-свитчинг между потоками — операция не бесплатная, прежде всего из-за сохранения и восстановления стека. За переключение отвечает планировщик ОС.
Семафоры ограничивают количество потоков, одновременно работающих с ресурсом — например, не более трёх одновременно. Локи работают жёстче: пока один поток пишет, другие не могут ни читать, ни писать.
PostgreSQL: индексы и транзакции без лишних слов
Самые частые темы на собесах по базам данных — индексы и уровни изоляции.
B-tree индекс — самый распространённый. Сложность поиска O(log n), вставка тоже логарифмическая (нужно найти узел и, возможно, перебалансировать дерево). Подходит для большинства случаев.
GIN-индекс — для сложных структур: JSONB, массивы, полнотекстовый поиск. При полнотекстовом поиске текст преобразуется в tsvector, поиск идёт по вектору.
Ключевое понятие — селективность: насколько уникальны значения поля. Поле email — высокая селективность, индекс оправдан. Поле gender с тремя возможными значениями — низкая селективность, B-tree индекс не поможет. Можно построить — но планировщик PostgreSQL скорее всего всё равно выберет Seq Scan.
EXPLAIN ANALYZE — запускает запрос реально, показывает фактическое время и количество строк. На продакшн-базе использовать с осторожностью: тяжёлый запрос нагрузит сервер. Смотреть нужно на тип сканирования (Seq Scan без индекса, Index Scan с индексом), количество строк (rows) и стоимость (cost).
SELECT FOR UPDATE блокирует строки до конца транзакции. Применяется в паттерне «прочитай-вычисли-запиши», чтобы никто не изменил строку в промежутке. Полезно, но увеличивает время ответа — использовать точечно.
MVCC (Multi-Version Concurrency Control) — механизм версионирования: при записи создаётся новая версия, читатели видят предыдущую. Это позволяет чтению не блокировать запись.
По уровням изоляции: READ COMMITTED — стандарт для большинства задач. SERIALIZABLE — выстраивает транзакции последовательно, исключает все аномалии (фантомное чтение, неповторяемое чтение), но жертвует производительностью. Используется в финансовых системах, где аномалии недопустимы.
Redis: что реально спрашивают
Redis по умолчанию однопоточный. Это значит, что команды выполняются последовательно. KEYS * перебирает все ключи базы — на нагруженном инстансе с миллионами ключей это блокирует все операции на секунды. На продакшне запрещена. Вместо неё — SCAN с итерацией и курсором.
Redis Cluster использует мастер-ноды и реплики. Ключи распределяются по нодам через алгоритм консистентного хэширования (hash ring). Преимущество: при добавлении или удалении ноды перераспределяется минимальное количество ключей, а не все сразу — в отличие от классического модульного хэширования.
Метрики качества кэша: hit rate (доля запросов, нашедших данные в кэше) и miss rate (доля промахов). Если пользователи запрашивают уникальные данные — кэш будет постоянно промахиваться и лишь добавит latency. Кэш имеет смысл только при достаточно высоком hit rate.
Сериализация объектов для Redis: JSON — самый простой, бинарные форматы (MessagePack и аналоги) — занимают меньше места и быстрее при передаче, но требуют CPU на упаковку/распаковку. pickle работает только внутри Python.
Универсальный роадмап: 7 блоков для любого бэкенд-стека
Разберём по блокам то, что реально проверяют на собеседованиях — от скрининга до Senior-раунда.
Блок 1. Computer Science и многопоточность
Самый заезженный вопрос на скрининге: процесс vs поток. Процесс — это запущенный экземпляр приложения со своей областью памяти. Потоки существуют внутри процесса и делят его общую память. Между процессами прямой доступ к памяти друг друга невозможен — только через IPC (inter-process communication).
Виды IPC, которые нужно перечислить: файл, сигнал, сокет, конвейер, именованный канал, семафор, общая память, очереди сообщений. Файлы, сигналы и сокеты работают во всех ОС. Остальное — преимущественно POSIX (Linux).
Лёгкие потоки в разных языках называются по-разному: - Go — горутины - Kotlin — корутины - PHP — файберы - Java — виртуальные треды (Green Threads)
Две модели межпотокового взаимодействия: классическая через общую память (большинство языков) и CSP (теория последовательных процессов) — каналы, селекты, горутины. Последнее — специфика Go.
Конкурентность vs параллелизм — частый вопрос. Конкурентность: несколько задач в процессе выполнения одновременно (могут не выполняться буквально в один момент). Параллелизм: задачи выполняются буквально одновременно на разных ядрах или процессорах.
Блок 2. Сети и протоколы
HTTP 1.1 и HTTP/2 — обязательно. HTTP/2 лежит в основе gRPC.
gRPC — не самостоятельная технология, а частный случай RPC (Remote Procedure Call). Работает поверх HTTP/2, использует Protocol Buffers для сериализации. Знание, что существует ещё и SOAP как второй вид RPC (устаревший, но живой в корпоративных системах) — это уже уровень Senior на собесе.
JWT — просто формат токена. Не аутентификация, не авторизация. Частая путаница на собесах — смешивать эти понятия.
Блок 3. Безопасность
Аутентификация vs авторизация — самый частый вопрос блока на скрининге.
Виды аутентификации: - Basic Auth — устаревший, допустим только для небольших внутренних систем - OAuth 2.0 + OpenID Connect — стандарт для большинства современных систем, реализует SSO - Keycloak — готовое опенсорс-решение для OAuth 2.0, разворачивается как сервис, настраивается через UI
Виды авторизации: - RBAC — доступ по ролям - ABAC — доступ по атрибутам объекта и субъекта - Policy-based — доступ по правилам (используется в OPA и аналогах)
Блок 4. Микросервисы
Тема обязательна для мидла. На скрининге спросят отличие от монолита. На Senior — паттерны.
Паттерны данных: - CQRS — разделение операций чтения и записи на разные модели - Saga — управление транзакционностью между сервисами через компенсирующие действия - Database per Service — каждый сервис владеет своей базой - Eventual Consistency — финальная согласованность. Перед собесом стоит знать теорему CAP
Transactional Outbox — тема уровня Senior, встречается редко, но знание выделяет.
Паттерны отказоустойчивости: - Retry — повторы при сбоях с экспоненциальным backoff - Circuit Breaker — при систематических сбоях функционал отключается, не нагружая систему - Bulkhead — изоляция отказа: если тонет один компонент, остальные работают (аналогия с переборками корабля)
Паттерны развёртывания: - Canary Release — поэтапный выкат: 1% → 10% → 20% → все пользователи - Feature Toggle — включение/отключение функционала без деплоя
Блок 5. Продакшн-инфраструктура
Мониторинг: Prometheus собирает метрики, Grafana их визуализирует. Grafana сама по себе метрики не собирает — это частая путаница на собесах.
Трассировка: OpenTelemetry — наиболее популярный стандарт. Jaeger и Zipkin — конкретные реализации.
По Kubernetes достаточно уверенно работать как пользователь: kubectl, подключиться к поду, посмотреть логи, зайти в консоль пода. Глубокое знание Kubernetes-контроллеров — это отдельная специализация (и отдельная зарплата).
Очереди сообщений: Kafka, RabbitMQ и NATS покрывают подавляющее большинство собеседований. Ansible и Jenkins — DevOps-тематика, бэкенд-разработчику знать необязательно.
Блок 6. Базы данных
SQL-темы уровня Senior:
- Оконные функции (OVER, PARTITION BY, ROW_NUMBER) — встречаются на практических задачах
- Транзакции и уровни изоляции — обязательно
- ACID — атомарность, согласованность, изолированность, устойчивость
- EXPLAIN и EXPLAIN ANALYZE — умение читать план запроса
Индексы: B-tree — база. GIN — для JSONB и полнотекстового поиска. Bitmap и GiST — спрашиваются редко.
NoSQL: Redis и MongoDB — активно проверяются. Знание Redis в деталях (однопоточность, KEYS *, консистентное хэширование) выделяет кандидата.
Паттерны работы с данными: Repository — распространённый, Active Record — многие считают антипаттерном из-за смешивания бизнес-логики с доступом к данным.
Блок 7. Паттерны проектирования
Из классики «Банды четырёх» на собесах чаще всего встречаются:
Порождающие: Singleton, Фабричный метод, Абстрактная фабрика, Lazy Initialization.
Структурные: Декоратор, Фасад, Адаптер, Proxy.
Поведенческие: Стратегия, Observer (Наблюдатель), Команда.
Паттерны параллелизма: Thread Pool — очень популярен, особенно в Java. Double-Checked Locking — спрашивается реже, но знание нужно.
Архитектурные подходы: чистая архитектура со слоями domain/application/service/controller и гексагональная архитектура с портами и адаптерами — разные способы достичь одного результата: тестируемой доменной логики, не зависящей от конкретных технологий.
Практический раздел: что делать прямо сейчас
Если собес в ближайшие 2-4 недели — разберём по приоритетам.
Go-разработчикам:
1. Проговори вслух внутреннее устройство slice header и механизм роста capacity. Не читай — объясни, как будто перед интервьюером.
2. Разберись с новыми SwissTable map (Go 1.24): H1/H2 разбивка хеша, control bytes, SIMD-поиск.
3. Напиши тест с намеренной утечкой горутин через канал без получателя — убедись, что можешь воспроизвести и исправить.
4. Проверь, что понимаешь разницу wall clock / monotonic clock в time.Now().
5. Пройдись по escape-анализу: скомпилируй что-нибудь с флагом -gcflags="-m" и посмотри, что куда уходит.
Python-разработчикам:
1. Объясни GIL и его влияние на CPU-bound vs IO-bound задачи — без бумаги, вслух.
2. Разберись с SELECT FOR UPDATE и MVCC в PostgreSQL: когда применять, какие накладные расходы.
3. Попрактикуй EXPLAIN ANALYZE: возьми реальный запрос, посмотри план, найди Seq Scan там, где должен быть Index Scan.
4. Проверь: можешь ли ты объяснить, почему KEYS * в Redis опасен и что использовать вместо него?
5. Реализуй Dependency Injection в FastAPI через Depends — если ещё не делал в последнее время, освежи.
Всем, кто готовится к собесу по архитектуре: 1. Выучи Saga и CQRS с примерами — не определение, а когда применять. 2. Объясни разницу Circuit Breaker и Retry — это разные паттерны с разными сценариями применения. 3. Разберись с Canary Release на конкретном примере деплоя. 4. Проверь понимание Prometheus + Grafana: что именно делает каждый инструмент.
Что мы заметили: где подходы сходятся, где расходятся
Сходится во всех источниках:
Граница между Middle и Senior проходит не по знанию новых фреймворков, а по глубине понимания платформы. Знать, что append расширяет слайс — это Middle. Объяснить правила роста capacity, поведение при передаче в функцию и почему изменения длины не видны снаружи — это Senior.
Умение находить и объяснять продакшн-баги ценится выше, чем знание теоретических определений. Утечка горутин, race condition при параллельном доступе к map, неправильная семантика передачи slice — всё это реальные баги, которые стоят компаниям денег. Кандидат, который видит такой код и сразу объясняет механизм на уровне рантайма, — это тот, за кем охотятся.
Где подходы немного расходятся:
Более детальный разбор уделяет большое внимание внутреннему устройству конкретного языка — вплоть до разбивки хеша на H1/H2 и работы SIMD-инструкций. Это оправдано для позиций, где от тебя ожидают оптимизации производительности и знания платформы на уровне рантайма.
Роадмап-подход, напротив, ориентирован на ширину охвата: семь блоков, приоритеты по уровням, универсальность для любого стека. Здесь важнее знать, что такое CQRS и Saga, чем объяснять детали SwissTable. Это не противоречие — это разные фокусы для разных позиций.
Более консервативный взгляд на собеседование: интервьюер оценивает не только точность ответов, но и ход мысли. Рассуждение вслух, движение к правильному ответу через логику, умение сказать «не знаю точно, но логика подсказывает вот так» — это ценится. Противоположный подход: знания должны быть точными, потому что в продакшне нет интервьюера, который направит тебя в нужную сторону.
На наш взгляд, правда посередине: ход мысли важен, но только если он движется в правильном направлении. Полная неопределённость по базовым темам — например, не знать, что GIL влияет только на CPU-bound задачи — не вытягивается никаким «рассуждением вслух».
Итог
Senior Backend — это не тот, кто знает больше фреймворков. Это тот, кто может объяснить, что происходит под капотом, найти баг до того, как он попал в продакшн, и объяснить коллеге, почему именно так, а не иначе.
Семь блоков роадмапа дают карту. Разбор живых собесов по Go и Python показывает, что именно на этой карте проверяется острее всего. Возьми оба — и закрой пробелы не «для галочки», а так, чтобы объяснить вслух без подсказок. Именно это и есть разница между кандидатом, которому делают оффер, и тем, кому говорят «мы вам перезвоним».