Senior Level

Python Developer
Interview Guide

Полный справочник для подготовки к техническому собеседованию. Построен на основе реальной оценочной матрицы.

Каждый раздел содержит конкретные вопросы и ответы уровня Senior. Темы, помеченные провал, — те, на которых кандидаты чаще всего проваливаются. Уделите им особое внимание.

01 Структуры данных

Hash Table

Хэш-таблица — структура данных, обеспечивающая O(1) среднее время доступа по ключу.

Как работает

  1. Ключ пропускается через хэш-функцию → получаем целое число.
  2. Число приводится к индексу массива (обычно hash % capacity).
  3. По индексу хранится бакет (bucket) — слот для одного или нескольких элементов.

Зачем нужны бакеты

Разные ключи могут дать одинаковый индекс — это коллизия. Бакет позволяет хранить несколько пар (key, value) в одном слоте.

МетодОписание
Chaining (цепочки)Каждый бакет — связный список / массив. Python dict использует вариацию — open addressing.
Open AddressingПри коллизии ищем следующий свободный слот (linear probing, quadratic probing, double hashing).

В Python dict — open addressing с quadratic probing. Load factor ≤ ⅔ → при превышении таблица расширяется.

Linked List

Связный список — каждый элемент (node) хранит значение и указатель на следующий (Singly) или на оба направления (Doubly).

ОперацияArrayLinked List
Доступ по индексуO(1)O(n)
Вставка в началоO(n)O(1)
Вставка в конецO(1) amortizedO(1) с tail
Удаление по значениюO(n)O(n) + O(1)

B-Tree

Сбалансированное дерево для дисковых хранилищ. Каждый узел содержит до M ключей и M+1 потомков. Все листья на одной глубине. Используется в индексах PostgreSQL, файловых системах.

B+ Tree — данные только в листьях, а листья связаны в linked list → быстрый range scan.

Stack vs Queue

Stack (Стек)Queue (Очередь)
ПорядокLIFO — Last In, First OutFIFO — First In, First Out
Операцииpush, popenqueue, dequeue
Pythonlist или dequedeque или queue.Queue
ПрименениеDFS, undo, call stackBFS, планировщик, буферизация

Графы

Частый провал на интервью — «про графы рассказать не смог».

Граф = множество вершин (V) + множество рёбер (E). Виды:

# Adjacency List — наиболее частый вариант
graph = {
    'A': ['B', 'C'],
    'B': ['D'],
    'C': ['D'],
    'D': []
}
# Adjacency Matrix — O(1) проверка ребра, O(V²) память

02 Алгоритмы

Бинарный поиск

Поиск в отсортированном массиве за O(log n).

def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

DFS и BFS

DFS (Depth-First)BFS (Breadth-First)
СтруктураСтек (рекурсия или явный)Очередь
КогдаПоиск пути, topological sort, циклыКратчайший путь (невзвешенный)
СложностьO(V + E)O(V + E)
from collections import deque

def bfs(graph, start):
    visited = {start}
    queue = deque([start])
    while queue:
        node = queue.popleft()
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
    return visited

def dfs(graph, node, visited=None):
    if visited is None:
        visited = set()
    visited.add(node)
    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)
    return visited

Сортировки

Частый провал: «не смог ответить про сортировки».
АлгоритмСреднееХудшееПамятьСтабильная
Bubble SortO(n²)O(n²)O(1)Да
Insertion SortO(n²)O(n²)O(1)Да
Merge SortO(n log n)O(n log n)O(n)Да
Quick SortO(n log n)O(n²)O(log n)Нет
Timsort (Python)O(n log n)O(n log n)O(n)Да

Timsort — гибрид merge sort и insertion sort. Используется в sorted() и list.sort(). Оптимизирован для частично отсортированных данных.

03 Python Core

deep copy vs shallow copy

import copy

a = [[1, 2], [3, 4]]
b = copy.copy(a)       # shallow — новый список, но вложенные — те же объекты
c = copy.deepcopy(a)   # deep — полная рекурсивная копия

a[0].append(99)
# b[0] == [1, 2, 99]  — изменился!
# c[0] == [1, 2]      — не изменился

is vs ==

a = [1, 2]; b = [1, 2]
a == b   # True — значения равны
a is b   # False — разные объекты
a is None  # Единственное правильное is-сравнение с None

Float vs Decimal

floatDecimal
ТочностьIEEE 754, ~15 цифр, неточенПроизвольная точность
СкоростьБыстрый (аппаратный)~50-100x медленнее
КогдаНаучные, MLФинансы, деньги
0.1 + 0.2 == 0.3   # False!

from decimal import Decimal
Decimal('0.1') + Decimal('0.2') == Decimal('0.3')  # True

Контекстные менеджеры

class ManagedResource:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
        return False  # True — подавить исключение

# Проще через contextlib
from contextlib import contextmanager

@contextmanager
def managed():
    resource = acquire()
    try:
        yield resource
    finally:
        resource.release()

__call__

Делает экземпляр класса вызываемым как функция:

class Multiplier:
    def __init__(self, factor):
        self.factor = factor
    def __call__(self, x):
        return x * self.factor

double = Multiplier(2)
double(5)  # 10

__getattribute__ vs __getattr__

Частый провал: «не смог ответить про __getattribute__ vs __getattr__».
__getattribute____getattr__
КогдаВсегда при доступе к атрибутуТолько когда атрибут не найден
ПорядокВызывается первымFallback
ОпасностьБесконечная рекурсияБезопасен
class Demo:
    x = 10

    def __getattribute__(self, name):
        print(f"accessing {name}")
        return super().__getattribute__(name)  # ОБЯЗАТЕЛЬНО super()!

    def __getattr__(self, name):
        return f"{name} not found"

d = Demo()
d.x       # "accessing x" → 10
d.missing # "accessing missing" → "missing not found"

MRO (Method Resolution Order)

Порядок поиска методов при множественном наследовании. Алгоритм — C3 Linearization.

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

D.__mro__  # (D, B, C, A, object)

__slots__

Ограничивает атрибуты экземпляра. Нет __dict__ → экономия памяти + быстрый доступ.

class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x; self.y = y

p = Point(1, 2)
p.z = 3  # AttributeError!

Замыкания (Closures)

def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
c()  # 1
c()  # 2

Protocol и Generics

from typing import Protocol, TypeVar, Generic

class Drawable(Protocol):
    def draw(self) -> None: ...  # Структурная типизация

T = TypeVar('T')

class Stack(Generic[T]):
    def __init__(self): self._items: list[T] = []
    def push(self, item: T): self._items.append(item)
    def pop(self) -> T: return self._items.pop()

Protocol — structural subtyping. Класс не нужно наследовать — достаточно реализовать нужные методы.

04 ООП, метаклассы, перегрузка

Метаклассы

Частый провал: «не смог ответить про метаклассы».

Метакласс — «класс классов». Класс — экземпляр метакласса. По умолчанию — type.

class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    pass

a = Database()
b = Database()
a is b  # True

Поток: type.__new__ создаёт класс → type.__init__ инициализирует → type.__call__ создаёт экземпляр.

Перегрузка методов

Частый провал: «не смог ответить про перегрузку методов».

В Python нет классической перегрузки. Но есть механизмы:

# 1. typing.overload — только для type checkers
from typing import overload

@overload
def process(x: int) -> int: ...
@overload
def process(x: str) -> str: ...

def process(x):
    if isinstance(x, int): return x * 2
    return x.upper()

# 2. singledispatch — runtime dispatch по типу первого аргумента
from functools import singledispatch

@singledispatch
def handle(arg):
    raise NotImplementedError

@handle.register(int)
def _(arg): return arg ** 2

@handle.register(str)
def _(arg): return arg.upper()

Интерфейс vs Абстрактный класс

ABCProtocol
ТипизацияНоминальная (наследование)Структурная (duck typing)
RuntimeДа (isinstance)Только type checker
РеализацияМожет содержатьТолько сигнатуры

Идемпотентность и чистая функция

Частый провал: «не смог ответить про идемпотентность, чистую функцию».

Идемпотентность — повторный вызов с тем же входом даёт тот же эффект. PUT /users/1 — идемпотентен; POST /users — нет.

Чистая функция: (1) при одних аргументах — всегда одинаковый результат; (2) нет побочных эффектов.

# Чистая
def add(a, b): return a + b

# Не чистая
total = 0
def add_to_total(x):
    global total
    total += x
    return total

05 GIL и многопоточность

GIL (Global Interpreter Lock)

Мьютекс CPython — только один поток исполняет Python-байткод одновременно.

ЗадачаРешение
I/O-bound (запросы, БД, файлы)asyncio или threading
CPU-bound (вычисления)multiprocessing или C-расширения

Python 3.12+: Per-interpreter GIL. Python 3.13+: Экспериментальный free-threaded CPython (без GIL).

06 Django & DRF

select_related vs prefetch_related

Частый провал: «не смог ответить про select_related».
select_relatedprefetch_related
КакSQL JOIN — один запросОтдельный запрос + Python join
СвязиFK, OneToOneM2M, обратные FK
# N + 1 проблема:
books = Book.objects.all()
for b in books:
    print(b.author.name)  # Каждый раз запрос!

# Решение:
books = Book.objects.select_related('author').all()  # 1 запрос с JOIN

APIView vs ViewSet

APIViewViewSet
КонтрольПолный — get, post, putCRUD через list, create, retrieve...
URLРучное подключениеАвтоматический Router
КогдаНестандартная логикаСтандартный CRUD

Mixins в DRF

Миксины добавляют CRUD: CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin. Комбинируются с GenericAPIView.

07 FastAPI, WSGI, ASGI

WSGI vs ASGI

Частый провал: «не смог ответить про WSGI, ASGI».
WSGIASGI
МодельСинхронная, 1 запрос = 1 потокАсинхронная, event loop
ПротоколыHTTPHTTP, WebSocket, HTTP/2
ФреймворкиDjango (default), FlaskFastAPI, Starlette
СерверыGunicorn, uWSGIUvicorn, Hypercorn
# WSGI
def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [b'Hello']

# ASGI
async def app(scope, receive, send):
    await send({'type': 'http.response.start', 'status': 200})
    await send({'type': 'http.response.body', 'body': b'Hello'})

FastAPI: Depends и response_model

from fastapi import FastAPI, Depends
from pydantic import BaseModel

app = FastAPI()

class UserOut(BaseModel):
    id: int
    name: str

async def get_db():
    db = SessionLocal()
    try:
        yield db            # DI + контекстный менеджер
    finally:
        db.close()

@app.get("/users/{id}", response_model=UserOut)
async def get_user(id: int, db=Depends(get_db)):
    return db.query(User).get(id)

08 asyncio

Coroutine vs Task vs Future

СущностьЧто этоКак создать
CoroutineОбъект от async def. Не запущен!coro = my_func()
TaskОбёртка, запланирована в event loopasyncio.create_task(coro)
FutureКонтейнер для будущего результатаloop.create_future()

create_task / gather / wait_for

import asyncio

task = asyncio.create_task(fetch_data())               # параллельный запуск
results = await asyncio.gather(a(), b(), c())          # ждём все

try:
    r = await asyncio.wait_for(slow(), timeout=5.0)   # с таймаутом
except asyncio.TimeoutError: ...

asyncio.run(main())                                    # точка входа

run_in_executor

async def main():
    loop = asyncio.get_running_loop()
    result = await loop.run_in_executor(None, blocking_io)         # thread pool
    result = await loop.run_in_executor(ProcessPoolExecutor(), cpu)  # process pool

Примитивы синхронизации

Частый провал: «не смог ответить про примитивы синхронизации».
ПримитивОписание
LockТолько один await за раз
EventСигнализация между корутинами
SemaphoreОграничение параллелизма (n одновременно)
ConditionLock + Event, ожидание условия
QueueAsync-очередь producer/consumer
sem = asyncio.Semaphore(10)  # макс 10 одновременно

async def fetch(url):
    async with sem:
        return await http_client.get(url)

09 Celery

Распределённая очередь задач: ProducerBroker (RabbitMQ / Redis) → WorkerResult Backend.

from celery import Celery

app = Celery('tasks', broker='redis://localhost:6379/0')

@app.task(bind=True, max_retries=3, default_retry_delay=60)
def send_email(self, to, subject, body):
    try:
        smtp_send(to, subject, body)
    except ConnectionError as exc:
        raise self.retry(exc=exc)

send_email.delay("user@example.com", "Hi", "Body")     # async
send_email.apply_async(args=[...], countdown=300)     # через 5 мин

Композиция: chain, group, chord; celery beat — планировщик; acks_late — подтверждение после выполнения.

10 API, REST, RPC

Модель OSI

#УровеньПримеры
7ApplicationHTTP, FTP, SMTP, DNS
6PresentationSSL/TLS, JPEG, JSON
5SessionСессии, RPC
4TransportTCP, UDP
3NetworkIP, ICMP
2Data LinkEthernet, MAC
1PhysicalКабели, Wi-Fi

REST vs RPC

RESTRPC (gRPC)
ОриентацияРесурсы (/users/1)Процедуры (GetUser(1))
ПротоколHTTP + JSONHTTP/2 + Protobuf
СтримингНет (WebSocket)Встроенный bidirectional

SemVer

MAJOR.MINOR.PATCH: MAJOR — ломающие изменения, MINOR — новая функциональность, PATCH — баг-фиксы.

Circuit Breaker

Защита от каскадных отказов. Три состояния: Closed (нормально, считаем ошибки) → Open (fail fast, всё отклоняется) → Half-Open (пробные запросы).

Stateless vs Stateful

Monkey Patching

Частый провал: «не смог ответить про Monkey patching».

Динамическая замена атрибутов/методов в runtime:

some_module.original_function = patched_function  # Monkey patch

# В тестах — pytest monkeypatch:
def test_something(monkeypatch):
    monkeypatch.setattr(requests, 'get', mock_get)

Применение: тестирование, gevent/eventlet. Опасно в продакшене.

11 HTTP & RESTful

HTTP-методы

МетодИдемпотентныйSafeТело
GETДаДаНет
POSTНетНетДа
PUTДаНетДа
PATCHНе гарантированНетДа
DELETEДаНетОпционально

Коды ответов

12 Теория РСУБД

Проблема N + 1

1 запрос для списка + N запросов для связанных данных каждого элемента. Решение: JOIN, select_related, joinedload.

ACID

СвойствоОписание
AtomicityТранзакция выполняется полностью или не выполняется
ConsistencyБД из одного валидного состояния в другое
IsolationПараллельные транзакции не влияют друг на друга
DurabilityЗакоммиченные данные сохраняются при сбое

Уровни изоляции

УровеньDirty ReadNon-RepeatablePhantom
Read UncommittedДаДаДа
Read Committed (PG default)НетДаДа
Repeatable ReadНетНетДа*
SerializableНетНетНет

13 PostgreSQL

Индексы

ТипКогда использовать
B-tree (default)=, <, >, BETWEEN, ORDER BY
HashТолько равенство
GiSTПолнотекст, гео, ranges
GINJSONB, массивы, fulltext
BRINОгромные таблицы с физически упорядоченными данными

EXPLAIN vs EXPLAIN ANALYZE

EXPLAINEXPLAIN ANALYZE
Что делаетПоказывает план (оценки)Выполняет + реальные метрики
Запрос выполняется?НетДа
-- Seq Scan — полное сканирование (медленно)
-- Index Scan — через индекс (быстро)
-- Index Only Scan — данные из индекса (ещё быстрее)

EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'a@b.com';
-- Если Seq Scan — нужен индекс!

Shared Lock vs Exclusive Lock

Shared (FOR SHARE)Exclusive (FOR UPDATE)
ЧтениеРазрешено всемРазрешено
ЗаписьБлокированаБлокирована для других

Deadlock

Частый провал: «не смог ответить про Deadlock».

Deadlock — взаимная блокировка: Tx1 ждёт ресурс Tx2, и наоборот.

-- Tx1: UPDATE accounts SET balance=100 WHERE id=1;  -- блокирует 1
-- Tx2: UPDATE accounts SET balance=200 WHERE id=2;  -- блокирует 2
-- Tx1: UPDATE accounts SET balance=300 WHERE id=2;  -- ждёт 2
-- Tx2: UPDATE accounts SET balance=400 WHERE id=1;  -- ждёт 1 → DEADLOCK!

PG автоматически обнаруживает и откатывает одну транзакцию. Предотвращение: блокировать в одинаковом порядке; NOWAIT; SKIP LOCKED.

Constraints

Частый провал: «не смог ответить про constraints, views».

Views

-- Обычная View
CREATE VIEW active_users AS
  SELECT * FROM users WHERE is_active = true;

-- Materialized View — кэшированный результат
CREATE MATERIALIZED VIEW monthly_stats AS
  SELECT date_trunc('month', created_at) as month, count(*)
  FROM orders GROUP BY 1;

REFRESH MATERIALIZED VIEW monthly_stats;

Репликация / Шардирование / Партиционирование

РепликацияШардированиеПартиционирование
ЧтоКопии на другие серверыРазделение между серверамиРазделение внутри сервера
ЗачемОтказоустойчивость, read scalingГоризонтальное масштабированиеПроизводительность запросов

14 NoSQL & Redis

Типы NoSQL

ТипПримерыКогда
DocumentMongoDBГибкая схема, вложенные документы
Key-ValueRedis, DynamoDBКэш, сессии
Column-familyClickHouse, CassandraАналитика, OLAP
GraphNeo4jСвязи, рекомендации

Redis

In-memory store. Структуры: String, Hash, List, Set, Sorted Set, Stream.

Применения

Eviction: noeviction, allkeys-lru, volatile-lru, allkeys-lfu, volatile-ttl.

15 Брокеры сообщений

Queue vs Pub/Sub vs Topic

ПаттернОписаниеДоставка
QueuePoint-to-point: один consumerCompeting consumers
Pub/SubBroadcast: все подписчикиFan-out
TopicPub/Sub с фильтрациейПо ключу/паттерну

Гарантии доставки

ГарантияОписание
At most onceМожет потеряться, не дублируется
At least onceМинимум раз, возможны дубли
Exactly onceРовно раз (очень сложно)

16 RabbitMQ

Виды Exchange

Частый провал: «не смог ответить про виды Exchange».
TypeRoutingПример
DirectТочное совпадение routing keykey="order.created"
FanoutВо все очереди (broadcast)Уведомления
TopicПаттерны (*, #)order.*
HeadersПо заголовкам сообщенияСложная маршрутизация

17 Kafka

Распределённый лог (append-only): ProducerTopic (Partitions) → Consumer Group.

18 System Design

C4 Model

  1. Context — система + окружение (пользователи, внешние системы)
  2. Container — развёртываемые единицы (API, БД, frontend)
  3. Component — модули внутри контейнера
  4. Code — классы, интерфейсы

Микросервисы vs Монолит

МонолитМикросервисы
ДеплойВсё вместеНезависимо
СложностьКод ↑, инфра ↓Код ↓, инфра ↑
КогдаСтарт, маленькая командаБольшие команды, домены

19 Паттерны проектирования

Singleton

class Singleton:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

Adapter

Оборачивает объект с несовместимым интерфейсом:

class NewPaymentAdapter:
    def __init__(self, old: OldPayment):
        self._old = old
    def pay(self, amount):
        return self._old.make_payment(amount)

Facade

Простой интерфейс к сложной подсистеме. Скрывает внутреннюю сложность за одним методом.

Observer vs Mediator

Частый провал: «не смог ответить про Observer vs Mediator».
ObserverMediator
СвязьОдин-ко-многимМногие-ко-многим через посредника
ЗнаниеSubject знает об observersКомпоненты знают только mediator
ПримерDjango signals, event listenersЧат-комната, диспетчер
class EventEmitter:
    def __init__(self):
        self._listeners = {}
    def on(self, event, callback):
        self._listeners.setdefault(event, []).append(callback)
    def emit(self, event, *args):
        for cb in self._listeners.get(event, []):
            cb(*args)

20 SOLID & Принципы

SOLID

Частый провал: «не смог полноценно ответить про SOLID».
ПринципПростыми словами
SSingle ResponsibilityОдна причина для изменения
OOpen/ClosedОткрыт для расширения, закрыт для модификации
LLiskov SubstitutionПодтип заменяем базовым без поломки
IInterface SegregationМного маленьких интерфейсов лучше одного большого
DDependency InversionЗависимость от абстракций, не от реализаций
Примеры SOLID на Python
# S — Single Responsibility
# Плохо: класс и сохраняет, и отправляет email
# Хорошо: отдельный UserRepository и EmailService

# O — Open/Closed
class Discount(ABC):
    @abstractmethod
    def calculate(self, price): ...

class SeasonalDiscount(Discount):
    def calculate(self, price): return price * 0.8

# L — Liskov Substitution
# Bird.fly() не должен бросать NotImplementedError для Penguin

# I — Interface Segregation
class Readable(Protocol):
    def read(self) -> str: ...

class Writable(Protocol):
    def write(self, data: str): ...

# D — Dependency Inversion
class OrderService:
    def __init__(self, repo: OrderRepository):  # абстракция
        self.repo = repo

DRY, KISS, YAGNI

Low Coupling / High Cohesion

21 Docker & Kubernetes

Docker

# Multi-stage build
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages \
     /usr/local/lib/python3.12/site-packages
COPY . .
RUN useradd -m appuser
USER appuser
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]

Docker Compose

services:
  api:
    build: .
    ports: ["8000:8000"]
    depends_on: [db, redis]
  db:
    image: postgres:16
    volumes: [pgdata:/var/lib/postgresql/data]
  redis:
    image: redis:7-alpine

volumes:
  pgdata:

Kubernetes

РесурсОписание
PodМинимальная единица деплоя
DeploymentRolling updates, ReplicaSet
ServiceСтабильный endpoint
ConfigMap / SecretКонфигурация / секреты
IngressHTTP routing, TLS
HPAАвтоскейлинг по нагрузке

22 Тестирование

Виды тестов

pytest: fixtures и mocks

import pytest
from unittest.mock import patch

@pytest.fixture
def db_session():
    session = create_session()
    yield session
    session.rollback()

@pytest.mark.parametrize("input,expected", [(1,2), (2,4)])
def test_double(input, expected):
    assert double(input) == expected

@patch("app.services.send_email")
def test_registration(mock_send):
    mock_send.return_value = True
    result = register_user("user@test.com")
    assert result.success
    mock_send.assert_called_once()

23 Безопасность

OWASP Top 10

УгрозаЗащита
SQL InjectionПараметризованные запросы, ORM
XSSЭкранирование, CSP
CSRFCSRF-токены, SameSite cookies
Broken AuthJWT + refresh, bcrypt, MFA

JWT

header.payload.signature. Stateless auth. Access token (короткий TTL) + Refresh token (длинный, httpOnly cookie).

24 UNIX & Git

Linux

Git (Senior level)