Как устроено собеседование Python-бэкендера
Собес на Python-бэкенда обычно состоит из 3-5 секций, растянутых на один или два дня. Структура плавает от компании к компании, но костяк почти всегда один:
- Язык и база — вопросы про сам Python: GIL, asyncio, генераторы, декораторы, типизацию. Проверяют, понимаешь ли ты механику, а не просто пользуешься синтаксисом.
- БД и SQL — транзакции, индексы, N+1 в ORM, работа с сессиями SQLAlchemy или Django ORM.
- Системный дизайн — проектирование сервиса: от API до очередей и кэша. На middle — упрощённый вариант, на senior — с акцентом на компромиссы и отказоустойчивость.
- Алгоритмы и структуры данных — не везде, но встречается: обычно задачи попроще, чем в биг-техе, без hardcore leetcode hard.
- Лайвкодинг — написать рабочий код здесь и сейчас: иногда это рефакторинг существующего куска, иногда — небольшая фича с нуля.
От middle ждут уверенного знания языка и способности написать рабочий код без подсказок. От senior — ещё и умения объяснить, почему выбрано то или иное решение, какие есть альтернативы и где они себя сломают на масштабе. Senior, который не может аргументировать выбор между threading и asyncio для конкретной задачи, выглядит слабее middle, который это объяснил.
Вопросы по языку: GIL и конкурентность
Что такое GIL и зачем он нужен?
Global Interpreter Lock — мьютекс, который позволяет только одному потоку выполнять байткод Python в любой момент времени. Нужен для упрощения управления памятью CPython (refcounting не потокобезопасен сам по себе без лишних блокировок на каждый объект).
Значит ли GIL, что многопоточность в Python бесполезна?
Нет. GIL освобождается на I/O-операциях, поэтому threading хорошо работает для I/O-bound задач (сеть, файлы). Для CPU-bound нужен multiprocessing или C-расширения, которые отпускают GIL.
В чём разница между asyncio и threading?
Threading — вытесняющая многозадачность на уровне ОС с переключением контекста потоков. Asyncio — кооперативная многозадачность внутри одного потока: переключение происходит явно в точках await, накладные расходы меньше, но одна долгая синхронная операция блокирует весь event loop.
Что произойдёт, если в асинхронном коде вызвать блокирующую функцию типа time.sleep?
Event loop заблокируется целиком — ни одна другая корутина не выполнится, пока sleep не завершится. Нужно использовать asyncio.sleep или выносить блокирующий вызов в executor через run_in_executor.
Как работает event loop изнутри?
Это цикл, который держит очередь задач и колбэков, по кругу проверяет готовые сокеты через select/epoll и выполняет корутины до ближайшего await, передавая управление дальше. Корутина — это, по сути, state machine, которая сохраняет точку возобновления.
Вопросы по языку: память и объектная модель
Как CPython управляет памятью?
Основной механизм — подсчёт ссылок (reference counting): у каждого объекта есть счётчик, при обнулении объект удаляется сразу. Для циклических ссылок, которые refcounting не ловит, есть поколенческий garbage collector (generational GC), который периодически ищет недостижимые циклы.
Что такое MRO и зачем он нужен?
Method Resolution Order — порядок, в котором Python ищет метод при множественном наследовании. Строится по алгоритму C3-линеаризации, доступен через ClassName.__mro__. Нужен, чтобы однозначно разрешать конфликты имён методов в иерархии классов.
Чем дескриптор отличается от обычного атрибута?
Дескриптор — объект, определяющий __get__, __set__ или __delete__; через него работают property, методы классов, slots. Он перехватывает доступ к атрибуту на уровне класса, что позволяет строить ORM-поля, валидацию и кэширование вычисляемых значений.
В чём разница между генератором и обычной функцией, возвращающей список?
Генератор вычисляет значения лениво, по одному, и хранит в памяти только текущее состояние — это важно для больших или бесконечных последовательностей. Функция со списком сразу считает и хранит весь результат.
Как работает contextmanager и зачем нужен __exit__?
Контекстный менеджер гарантирует освобождение ресурса даже при исключении: __enter__ выделяет ресурс, __exit__ вызывается всегда, получает информацию об исключении и может его подавить, вернув True. Это стандартный способ управлять файлами, соединениями, локами.
Вопросы по языку: декораторы и типизация
Как декоратор сохраняет сигнатуру оборачиваемой функции?
Через functools.wraps, который копирует __name__, __doc__ и другие метаданные из исходной функции во враппер. Без него интроспекция, документация и некоторые фреймворки начинают путать оборачиваемую функцию с враппером.
Зачем нужна типизация в Python, если он динамически типизированный?
Подсказки типов (type hints) не меняют рантайм-поведение, но дают статическую проверку через mypy/pyright, улучшают автодополнение в IDE и служат документацией контракта функции. На рантайме типы никто не проверяет без явной валидации, например через pydantic.
Чем Protocol отличается от ABC в typing?
ABC — номинативная типизация: класс обязан явно унаследоваться от абстрактного класса. Protocol — структурная типизация (duck typing с проверкой на этапе статического анализа): класс подходит под протокол, если у него есть нужные методы, без явного наследования.
БД и системный дизайн
Для Python-бэкендера вопросы про БД почти всегда крутятся вокруг ORM, потому что именно там прячутся реальные продакшн-баги.
Что такое проблема N+1 и как её найти в Django ORM или SQLAlchemy?
Это ситуация, когда для списка из N объектов на каждый делается отдельный запрос к связанной таблице вместо одного JOIN. Находится через django-debug-toolbar, логирование SQL или echo=True в SQLAlchemy, лечится select_related/prefetch_related или joinedload/selectinload.
В чём разница между session.commit() и session.flush() в SQLAlchemy?
flush отправляет накопленные изменения в БД внутри текущей транзакции, но не фиксирует её — можно откатить. commit фиксирует транзакцию окончательно и закрывает её, после чего объекты сессии считаются expired и при обращении перечитываются из БД.
Как бы ты спроектировал сервис коротких ссылок на FastAPI?
Нужен генератор коротких кодов (base62 от инкрементного id или хэш), хранилище ключ-значение с индексом по short_code, кэш перед БД для горячих ссылок, асинхронные хендлеры для редиректов и отдельный воркер на Celery для сбора аналитики кликов, чтобы не замедлять редирект.
Дальше обычно спрашивают про уровни изоляции транзакций и про то, как БД вообще читает данные без блокировок — это общие для любого стека темы, но Python-кандидата часто просят привязать их к конкретному ORM: что будет при двух параллельных запросах через один и тот же SQLAlchemy engine, как ведёт себя Django при REPEATABLE READ.
Типичные ошибки кандидатов
- Путают параллелизм и конкурентность. Говорят "asyncio даёт параллельное выполнение", хотя на деле это один поток с кооперативным переключением — параллелизм появляется только при реальных процессах или C-расширениях вне GIL.
- Не могут объяснить, почему GIL вообще существует. Отвечают "потому что так сделали", вместо того чтобы связать его с refcounting и упрощением управления памятью.
- Знают декораторы синтаксически, но плывут на вложенных декораторах с аргументами. Декоратор с параметрами — это функция, возвращающая декоратор, а не декоратор сам по себе; многие путаются в этом на лайвкодинге.
- Не умеют читать EXPLAIN и не видят N+1 в собственном коде. Пишут ORM-запросы, не включая логирование SQL, и искренне не понимают, почему эндпоинт на проде медленный.
- На системном дизайне сразу тянут Kafka и микросервисы. Усложняют архитектуру там, где хватило бы одного сервиса с очередью на Celery и Redis, и не могут объяснить, зачем нужна каждая добавленная часть.
- Теряются под давлением на уточняющих вопросах. Знают тему, но после второго "а почему именно так" замолкают — это проблема не знаний, а тренировки объяснять вслух.
Частая ловушка на лайвкодинге: кандидат пишет асинхронный код, но внутри корутины вызывает синхронную библиотеку для похода в БД или HTTP. Код не падает, но event loop блокируется на время запроса, и вся "асинхронность" эндпоинта оказывается фикцией. Интервьюер почти всегда об этом спрашивает отдельно.
Как готовиться: план по неделям
Четыре недели — реалистичный срок для уверенной подготовки, если заниматься по 1-2 часа в день.
- Неделя 1 — язык. GIL, asyncio и event loop, генераторы, декораторы, дескрипторы, MRO, память и GC. Цель — не просто помнить определения, а уметь нарисовать на доске, как это работает внутри.
- Неделя 2 — БД и ORM. N+1, сессии и транзакции в SQLAlchemy или Django ORM, индексы, уровни изоляции, EXPLAIN. Прогони на реальной базе несколько медленных запросов и посмотри план выполнения вживую.
- Неделя 3 — системный дизайн. Разбери 5-7 типовых задач: короткие ссылки, лента новостей, очередь задач на Celery, кэширование профиля. Для каждой проговори вслух компромиссы, а не просто нарисуй схему.
- Неделя 4 — симуляция и алгоритмы. Пройди пару мок-интервью целиком, с таймером и без подглядывания в решение. Параллельно закрой базовые структуры данных и типовые задачи на списки, словари, строки — без акцента на хитрые головоломки.
Главный критерий готовности — способность объяснить решение вслух человеку, который сразу задаёт уточняющие вопросы. Молча знать тему и уметь её отстоять под давлением — разные навыки, и второй тренируется только практикой в условиях, близких к реальному собесу.
Ещё разборы