Перейти к содержимому

Собеседование Python: вопросы и задачи для junior, middle и senior

Команда Interview Assistantтехнические вопросыpython

На собеседовании Python проверяют понимание модели языка, а не количество выученных методов: что такое изменяемые и неизменяемые типы и почему список в аргументе по умолчанию опасен, как работают генераторы и декораторы, что делает GIL и когда нужны потоки, процессы или asyncio. Для junior достаточно уверенных основ и простых задач, для middle добавляются внутреннее устройство dict, итераторы, ООП с MRO и асинхронность, для backend-разработчика уровня senior — производительность, память и проектирование сервисов. Готовиться лучше через вопросы «почему так работает» и пять-шесть типовых задач, которые вы решаете вслух.

Ниже — этапы интервью, темы по уровням, таблица «вопрос → слабый ответ → сильный ответ», задачи с проверенным кодом, ошибки и чек-лист. Общую структуру технических интервью разбирает статья про вопросы на техническом собеседовании.

Как проходит собеседование на Python-разработчика

Этапы зависят от компании, но обычно такие:

  1. Разговор с рекрутером — стек, опыт, ожидания. Здесь нужен короткий рассказ о себе.
  2. Техническое интервью — 45–90 минут вопросов по языку, стандартной библиотеке, фреймворкам (Django, FastAPI, Flask), базам данных. Часто идёт по резюме: спрашивают про то, что вы указали.
  3. Задачи и live coding — решить небольшую задачу в общем редакторе. Как вести себя в этом формате, описано в статье про live coding.
  4. Тестовое задание — у части компаний вместо live coding или до него; см. тестовое задание разработчика.
  5. 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.7 dict сохраняет порядок вставки — это часть языка.
  • 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 или потоки
Тяжёлые вычисления на CPUmultiprocessing или 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

  1. Составьте 30–40 вопросов по темам выше и ответьте на каждый вслух за минуту-две.
  2. Напишите без подсказок: декоратор, генератор, контекстный менеджер, LRU-кэш, группировку через defaultdict.
  3. Повторите стандартную библиотеку: collections, itertools, functools, heapq, asyncio.
  4. Выберите фреймворк из резюме и подготовьте рассказ о запросе от роутера до базы данных.
  5. Повторите SQL и HTTP: JOIN, индексы, транзакции, коды ответов, идемпотентность.
  6. Подготовьте историю про проблему в проекте: симптом, диагностика, причина, исправление.
  7. Прорепетируйте с кем-то или запишите себя на видео — так видны паузы и воду в ответах.
  8. Если времени мало, используйте план из статьи как подготовиться к собеседованию за день.

Где здесь помогает 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 и пара типовых задач на коллекции — эти темы встречаются чаще остальных.

Как отвечать, если не знаешь ответ?

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