Собеседование Python: вопросы и задачи для junior, middle и senior
На собеседовании Python проверяют понимание модели языка, а не количество выученных методов: что такое изменяемые и неизменяемые типы и почему список в аргументе по умолчанию опасен, как работают генераторы и декораторы, что делает GIL и когда нужны потоки, процессы или asyncio. Для junior достаточно уверенных основ и простых задач, для middle добавляются внутреннее устройство dict, итераторы, ООП с MRO и асинхронность, для backend-разработчика уровня senior — производительность, память и проектирование сервисов. Готовиться лучше через вопросы «почему так работает» и пять-шесть типовых задач, которые вы решаете вслух.
Ниже — этапы интервью, темы по уровням, таблица «вопрос → слабый ответ → сильный ответ», задачи с проверенным кодом, ошибки и чек-лист. Общую структуру технических интервью разбирает статья про вопросы на техническом собеседовании.
Как проходит собеседование на Python-разработчика
Этапы зависят от компании, но обычно такие:
- Разговор с рекрутером — стек, опыт, ожидания. Здесь нужен короткий рассказ о себе.
- Техническое интервью — 45–90 минут вопросов по языку, стандартной библиотеке, фреймворкам (Django, FastAPI, Flask), базам данных. Часто идёт по резюме: спрашивают про то, что вы указали.
- Задачи и live coding — решить небольшую задачу в общем редакторе. Как вести себя в этом формате, описано в статье про live coding.
- Тестовое задание — у части компаний вместо live coding или до него; см. тестовое задание разработчика.
- System design — для middle+ и senior: спроектировать сервис, обсудить очередь, кэш, хранилище. См. system design на собеседовании.
Для backend-вакансии Python-вопросы почти всегда соседствуют с SQL, HTTP, очередями и контейнерами. SQL-блок подробно разобран в статье про вопросы по SQL.
Что спрашивают по уровням
| Уровень | Темы | Что хотят увидеть |
|---|---|---|
| Junior | Типы данных, изменяемость, функции, исключения, list/dict/set, срезы, ООП | Точные ответы и умение написать простой код без подсказок |
| Middle | Генераторы, декораторы, GIL, asyncio, MRO, контекстные менеджеры, типизация, тесты | Понимание причин поведения и ограничений |
| Senior | Профилирование, память, конкурентность, проектирование API и сервисов | Решения с компромиссами и опыт разбора реальных проблем |
Это один путь роста: вопрос уровня junior на senior-интервью звучит с продолжением «а что будет, если…».
Типы и изменяемость
Самая частая группа вопросов в «python вопросы на собеседовании».
Изменяемые и неизменяемые. Неизменяемые: int, float, str, tuple, frozenset, bytes. Изменяемые: list, dict, set. Переменная в Python — имя, ссылающееся на объект; присваивание копирует ссылку, а не объект:
a = [1, 2]
b = a
b.append(3)
print(a) # [1, 2, 3]
Ключи словаря. Ключом dict и элементом set может быть только хешируемый объект. Список не подходит, кортеж подходит, если все его элементы хешируемы.
is и ==. == сравнивает значения, is — идентичность объектов. Сравнивать с None принято через is None.
Копирование. copy.copy создаёт поверхностную копию (вложенные объекты общие), copy.deepcopy копирует рекурсивно.
Ловушка с аргументом по умолчанию. Значение по умолчанию вычисляется один раз при определении функции, поэтому изменяемый объект там живёт между вызовами:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
Вариант def add_item(item, items=[]) накопил бы элементы между вызовами. Исправленный вариант выше при каждом вызове без второго аргумента создаёт новый список.
Ловушка с замыканием в цикле. [lambda: i for i in range(3)] вернёт три функции, каждая из которых при вызове даёт 2: i ищется в момент вызова, а не создания. Лечится значением по умолчанию lambda i=i: i или functools.partial.
Структуры данных и их сложность
list— динамический массив: доступ по индексу O(1), добавление в конец амортизированно O(1), вставка в начало и поиск значения O(n).dictиset— хеш-таблицы: поиск, вставка и удаление в среднем O(1). Начиная с Python 3.7dictсохраняет порядок вставки — это часть языка.collections.deque— быстрые операции с обоих концов;heapq— куча;Counterиdefaultdict— типичные помощники в задачах.
Что почти всегда спрашивают: чем list отличается от tuple (изменяемость, хешируемость, семантика «запись фиксированной структуры»), почему x in some_list медленнее x in some_set, как удалить дубликаты с сохранением порядка (list(dict.fromkeys(items))). Подробнее про сложность и структуры — алгоритмы и структуры данных на собеседовании.
Генераторы и итераторы
Итератор — объект с методом __next__; итерируемый — объект, у которого iter() возвращает итератор. Генератор — функция с yield: она возвращает итератор, который вычисляет значения лениво и держит состояние между вызовами.
def read_chunks(lines, size):
chunk = []
for line in lines:
chunk.append(line)
if len(chunk) == size:
yield chunk
chunk = []
if chunk:
yield chunk
print(list(read_chunks(range(5), 2))) # [[0, 1], [2, 3], [4]]
Главный аргумент за генераторы: память. Файл на гигабайты можно обработать построчно, не загружая целиком. Дополнительно спрашивают: чем генераторное выражение (x for x in data) отличается от списка [x for x in data] (ленивость, одноразовость), что делает yield from и что происходит после исчерпания генератора (StopIteration; повторный обход даст пустоту).
Декораторы
Декоратор — функция, принимающая функцию и возвращающая новую. Запись @timed — это work = timed(work). Важная деталь, которую часто проверяют, — functools.wraps: без неё потеряются __name__ и __doc__ обёрнутой функции.
import functools
import time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
print(f"{func.__name__}: {time.perf_counter() - start:.4f}s")
return wrapper
@timed
def work(n):
return sum(range(n))
Типичные продолжения: как сделать декоратор с параметрами (третий уровень вложенности: фабрика → декоратор → обёртка), как декорировать метод и класс, что такое замыкание и ключевое слово nonlocal.
GIL, потоки, процессы и asyncio
Что такое GIL. Global Interpreter Lock в стандартной реализации CPython: в каждый момент байткод Python выполняет только один поток. Поэтому потоки не ускоряют вычисления, нагружающие процессор, но хорошо работают на задачах ввода-вывода: пока поток ждёт сеть или диск, GIL освобождается. GIL не отменяет потребность в блокировках: составные операции вроде counter += 1 не атомарны, и threading.Lock по-прежнему нужен. Эта тема эволюционирует (в новых версиях CPython появляются сборки без GIL), поэтому полезно оговорить, о какой версии и реализации вы говорите.
Что выбрать.
| Задача | Подход |
|---|---|
| Много сетевых запросов или чтения файлов | asyncio или потоки |
| Тяжёлые вычисления на CPU | multiprocessing или concurrent.futures.ProcessPoolExecutor, либо нативные библиотеки вроде NumPy |
| Простая параллельная обработка | concurrent.futures |
asyncio. Один поток, цикл событий и кооперативная многозадачность: корутина отдаёт управление в точках await. Если внутри async def вызвать блокирующую функцию, например time.sleep или синхронный HTTP-клиент, встанет весь цикл. Для запуска нескольких корутин одновременно используют asyncio.gather или asyncio.TaskGroup:
import asyncio
async def fetch(i):
await asyncio.sleep(0.1) # имитация сетевого вызова
return i * 2
async def main():
results = await asyncio.gather(*(fetch(i) for i in range(5)))
print(results) # [0, 2, 4, 6, 8], около 0.1 с, а не 0.5 с
asyncio.run(main())
Частые вопросы: чем корутина отличается от задачи (Task планирует корутину на выполнение), как ограничить параллелизм (asyncio.Semaphore), что делать с блокирующим кодом (asyncio.to_thread или пул потоков).
ООП, MRO и магические методы
- Наследование и
super(). При множественном наследовании порядок поиска метода задаёт MRO (Method Resolution Order), вычисляемый алгоритмом C3. Посмотреть его можно черезClass.__mro__. __init__и__new__.__new__создаёт объект,__init__инициализирует уже созданный.@staticmethod,@classmethod,@property. Метод экземпляра получаетself, метод класса —cls, статический не получает ничего.__eq__и__hash__. Если переопределить__eq__, а__hash__нет, экземпляры станут нехешируемыми.- Контекстные менеджеры. Протокол
__enter__/__exit__или декораторcontextlib.contextmanager; используется для файлов, блокировок, транзакций. - Dataclass.
@dataclassгенерирует__init__,__repr__,__eq__; параметрfrozen=Trueделает экземпляры неизменяемыми.
Небольшой пример на MRO, который любят давать: классы D(B, C), где B и C наследуют A и вызывают super().hi(). Цепочка вызовов пройдёт D → B → C → A, потому что super() идёт по MRO, а не просто к «родителю».
Таблица: вопрос, слабый ответ, сильный ответ
| Вопрос | Слабый ответ | Сильный ответ |
|---|---|---|
| В чём разница list и tuple? | «Список изменяемый, кортеж нет» и всё | Изменяемость, хешируемость (кортеж можно использовать ключом, если элементы хешируемы), смысл: однородная коллекция против записи фиксированной структуры |
| Что делает GIL? | «Не даёт использовать потоки» | В CPython не позволяет двум потокам одновременно выполнять байткод: вычисления на CPU не ускоряются, ввод-вывод в потоках работает, а гонки по-прежнему возможны |
Почему нельзя def f(x, items=[])? | «Так не принято» | Значение по умолчанию вычисляется один раз при определении функции, список общий для всех вызовов; нужен None и создание внутри |
| Чем генератор лучше списка? | «Быстрее» | Ленив: хранит одно значение, а не всю последовательность; подходит для больших потоков данных; но одноразовый и без индексации |
| Когда asyncio, а когда потоки? | «Asyncio современнее» | Asyncio — для большого числа задач ввода-вывода с неблокирующими библиотеками; потоки — когда есть блокирующий код; CPU-нагрузка — процессы |
Что делает functools.wraps? | «Не знаю» | Копирует __name__, __doc__ и другие атрибуты исходной функции в обёртку, чтобы отладка, документация и интроспекция работали |
Как работает dict? | «Хранит пары ключ-значение» | Хеш-таблица: ключ хешируется, по хешу определяется ячейка; ключи должны быть хешируемыми; поиск в среднем O(1); порядок вставки сохраняется |
Чем is отличается от ==? | «Почти то же самое» | == сравнивает значения через __eq__, is — идентичность объекта в памяти; с None сравнивают через is |
| Что такое MRO? | «Порядок наследования» | Порядок, в котором Python ищет атрибут и метод в иерархии при множественном наследовании (C3); super() следует ему, а не «родителю» |
Сильный ответ короткий: определение, причина, ограничение, пример. Три предложения заменяют пятиминутный монолог.
Задачи с решениями
Задача 1. Накопление в аргументе по умолчанию
Что напечатает print(add_item(1), add_item(2)) для функции из раздела про изменяемость? Ответ: [1] [2], потому что при каждом вызове создаётся новый список. С items=[] было бы [1, 2] [1, 2]: оба вызова вернули бы один и тот же объект.
Задача 2. Сгруппировать анаграммы
Дан список слов, нужно сгруппировать анаграммы.
from collections import defaultdict
def group_anagrams(words):
groups = defaultdict(list)
for w in words:
groups["".join(sorted(w))].append(w)
return list(groups.values())
print(group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"]))
# [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]
Ключ — отсортированные буквы слова. Сложность O(n · k log k), где k — длина слова. Хороший ответ упоминает альтернативу: ключ из счётчика букв даёт O(n · k).
Задача 3. Декоратор замера времени
Реализовать декоратор, печатающий время выполнения функции. Решение — timed из раздела про декораторы. Проверяют три вещи: functools.wraps, *args, **kwargs и возврат результата функции, в том числе при исключении (try/finally).
Задача 4. LRU-кэш
Реализовать кэш на N элементов, вытесняющий давно не использованные.
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.data = OrderedDict()
def get(self, key):
if key not in self.data:
return None
self.data.move_to_end(key)
return self.data[key]
def put(self, key, value):
self.data[key] = value
self.data.move_to_end(key)
if len(self.data) > self.capacity:
self.data.popitem(last=False)
Обе операции за O(1). После put("a"), put("b"), get("a"), put("c") при capacity=2 в кэше останутся a и c. Если спросят про стандартный путь для кэширования результатов функции, ответ — functools.lru_cache.
Задача 5. Параллельные запросы
Выполнить пять независимых асинхронных вызовов и собрать результаты. Решение — asyncio.gather из раздела про asyncio: общее время близко к самому долгому вызову, а не к сумме. Дополнение для middle+: что произойдёт при исключении в одной из корутин (по умолчанию gather пробросит первое исключение) и как ограничить число одновременных вызовов семафором.
Backend-блок: что ещё спрашивают у Python backend разработчика
- Фреймворки. Django: ORM, миграции, middleware, проблема N+1 (
select_related,prefetch_related). FastAPI: типизация и валидация через Pydantic, зависимости, асинхронные эндпоинты. Flask: минимальное ядро и расширения. - WSGI и ASGI. WSGI — синхронный интерфейс между веб-сервером и приложением, ASGI — его асинхронный преемник с поддержкой WebSocket.
- База данных. Индексы, транзакции, уровни изоляции, миграции без простоя.
- Инфраструктура. Очереди (Celery, брокеры сообщений), кэш, Docker, логирование. Подход к выбору архитектуры — в статье про микросервисы и монолит.
- Тестирование.
pytest, фикстуры, моки, разница юнит- и интеграционных тестов. См. вопросы по тестированию.
Типичные ошибки на собеседовании
- Определения без примеров. «Декоратор — это функция, принимающая функцию» без демонстрации звучит как зубрёжка.
- Смешение понятий. Говорить, что asyncio ускоряет вычисления, или что GIL защищает от всех гонок.
- Решение без проговаривания. Интервьюер оценивает ход мысли: назовите идею, сложность и граничные случаи до написания кода.
- Пропущенные граничные случаи: пустой ввод,
None, дубликаты, большие данные. - Изобретение велосипеда. Не знать
Counter,defaultdict,heapq,itertoolsзначит писать больше кода и допускать больше ошибок. - Выдуманный опыт. Если вы не работали с Celery или FastAPI, скажите, что знаете о них, и объясните устройство. Честность лучше уверенной ошибки.
Чек-лист подготовки к собеседованию Python
- Составьте 30–40 вопросов по темам выше и ответьте на каждый вслух за минуту-две.
- Напишите без подсказок: декоратор, генератор, контекстный менеджер, LRU-кэш, группировку через
defaultdict. - Повторите стандартную библиотеку:
collections,itertools,functools,heapq,asyncio. - Выберите фреймворк из резюме и подготовьте рассказ о запросе от роутера до базы данных.
- Повторите SQL и HTTP: JOIN, индексы, транзакции, коды ответов, идемпотентность.
- Подготовьте историю про проблему в проекте: симптом, диагностика, причина, исправление.
- Прорепетируйте с кем-то или запишите себя на видео — так видны паузы и воду в ответах.
- Если времени мало, используйте план из статьи как подготовиться к собеседованию за день.
Где здесь помогает Interview Assistant
Interview Assistant — приложение для macOS и Windows, которое транскрибирует речь интервьюера в реальном времени и отвечает на вопрос сначала краткими тезисами, затем развёрнутым разбором. Для подготовки к собеседованию по Python это тренажёр: вы озвучиваете вопросы из списка, сравниваете свой ответ с тезисами и находите слабые места — например, GIL или MRO. Первые 20 минут бесплатны, этого достаточно, чтобы оценить формат.
Ассистент не заменяет знания. Он помогает структурировать ответ и вспомнить деталь, но рассказывать о своём опыте и решать задачу вы должны сами. Лучше всего работает связка: подготовка заранее и подсказка как страховка от волнения.
Частые вопросы
Сколько вопросов задают на собеседовании Python?
Техническое интервью обычно занимает 45–90 минут, за это время обсуждают 10–20 тем. Один вопрос нередко превращается в цепочку уточнений, поэтому глубина ответа важнее количества затронутых тем.
Чем отличается собеседование Python junior и middle?
У junior проверяют основы: типы данных, функции, исключения, коллекции, простые задачи. Middle должен объяснять внутреннее устройство (GIL, генераторы, декораторы, MRO, asyncio), выбирать подход к конкурентности и разбираться в причинах проблем.
Какие задачи дают на собеседовании Python?
Чаще всего это работа со строками и коллекциями: группировка, подсчёт частот, поиск дубликатов, пары с заданной суммой, LRU-кэш, обход структур. Иногда просят написать декоратор или небольшой асинхронный код. Такие задачи разбираются в статье про алгоритмы и структуры данных.
Нужно ли знать Django или FastAPI?
Для backend-позиций да, хотя бы один фреймворк: вопросы про ORM, валидацию, middleware и работу с базой встречаются на уровне middle почти всегда. Какой именно, видно по вакансии; базовые идеи (маршрутизация, сериализация, транзакции) переносятся между ними.
Что учить в первую очередь, если времени мало?
Изменяемость и ссылки, list/dict/set и их сложность, генераторы, декораторы, GIL и разницу между потоками, процессами и asyncio. Плюс SQL и пара типовых задач на коллекции — эти темы встречаются чаще остальных.
Как отвечать, если не знаешь ответ?
Скажите прямо, что не работали с этим, затем покажите рассуждение: что знаете о смежных вещах, как предположили бы поведение и как проверили бы его в интерпретаторе. Интервьюеры ценят честность и способность разбираться больше, чем уверенную неправду.