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 Алгоритмы

Big O Notation

Частый провал: кандидат «знает Big O, но не работал низкоуровнево».

Нотация для описания асимптотической сложности алгоритма — как растёт время/память при росте входных данных.

СложностьНазваниеПример
O(1)КонстантнаяДоступ по индексу, hash lookup
O(log n)ЛогарифмическаяБинарный поиск
O(n)ЛинейнаяПроход по массиву
O(n log n)Линейно-логарифмическаяMerge sort, Timsort
O(n²)КвадратичнаяBubble sort, вложенный цикл
O(2¹)ЭкспоненциальнаяНаивная рекурсия Фибоначчи

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

Поиск в отсортированном массиве за 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(). Оптимизирован для частично отсортированных данных.

Дейкстра и A*

Частый провал: «алгоритм Дейкстры и A* не смог назвать».
ДейкстраA*
ЗадачаКратчайший путь от одной вершины ко всемКратчайший путь к конкретной цели
ГрафВзвешенный, неотрицательные рёбраВзвешенный + эвристика
СтруктураPriority queue (min-heap)Priority queue + f(n) = g(n) + h(n)
СложностьO((V + E) log V)Зависит от эвристики
ПрименениеМаршрутизация, сетиИгры, навигация, карты
import heapq

def dijkstra(graph, start):
    dist = {start: 0}
    heap = [(0, start)]
    while heap:
        d, u = heapq.heappop(heap)
        if d > dist.get(u, float('inf')):
            continue
        for v, w in graph[u]:
            nd = d + w
            if nd < dist.get(v, float('inf')):
                dist[v] = nd
                heapq.heappush(heap, (nd, v))
    return dist

A* добавляет эвристику h(n) — оценку расстояния до цели. Если h допустима (не переоценивает), A* находит оптимальный путь и при этом исследует меньше вершин, чем Дейкстра.

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. Класс не нужно наследовать — достаточно реализовать нужные методы.

staticmethod vs classmethod

@staticmethod@classmethod
Первый аргументНет (self/cls не передаётся)cls — сам класс
ДоступНет доступа к классу/экземпляруДоступ к классу, но не к экземпляру
КогдаУтилита, логически связанная с классомАльтернативные конструкторы, фабрики
class Date:
    def __init__(self, year, month, day):
        self.year, self.month, self.day = year, month, day

    @classmethod
    def from_string(cls, s):          # альтернативный конструктор
        y, m, d = map(int, s.split('-'))
        return cls(y, m, d)

    @staticmethod
    def is_valid(s):                  # утилита без доступа к cls/self
        parts = s.split('-')
        return len(parts) == 3

__init__ vs __new__

__new____init__
Что делаетСоздаёт экземпляр (аллокация)Инициализирует уже созданный экземпляр
ВозвращаетНовый объектNone
Первый аргументclsself
Когда переопределятьImmutable типы, Singleton, кэшированиеОбычная инициализация
class Singleton:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)  # создаёт объект
        return cls._instance

    def __init__(self):
        self.value = 42  # инициализирует каждый раз

super()

Возвращает proxy-объект, делегирующий вызов методу следующего класса в MRO. Без аргументов (Python 3) — super() автоматически определяет текущий класс.

class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)   # вызывает Animal.__init__
        self.breed = breed

Критично при множественном наследованииsuper() следует MRO, обеспечивая cooperative inheritance (каждый __init__ вызывается ровно один раз).

Итератор vs Генератор

ИтераторГенератор
ЧтоОбъект с __iter__ + __next__Функция с yield (или генераторное выражение)
СостояниеРучное управление в классеАвтоматическое (стек замороженной функции)
ЛенивостьДаДа
# Итератор (класс)
class Countdown:
    def __init__(self, n): self.n = n
    def __iter__(self): return self
    def __next__(self):
        if self.n <= 0: raise StopIteration
        self.n -= 1
        return self.n + 1

# Генератор (проще)
def countdown(n):
    while n > 0:
        yield n
        n -= 1

# Генераторное выражение
squares = (x**2 for x in range(10))

Garbage Collection (GC)

CPython использует два механизма сборки мусора:

  1. Reference counting — основной. Счётчик ссылок на объект; при 0 — объект немедленно удаляется.
  2. Generational GC — для циклических ссылок. Три поколения (0, 1, 2). Молодые объекты проверяются чаще.
import gc
gc.collect()           # принудительный запуск
gc.get_threshold()     # (700, 10, 10) — пороги поколений
gc.disable()           # отключить GC (reference counting продолжает работать)

Циклические ссылки: a.ref = b; b.ref = a — ref count никогда не станет 0, поэтому нужен generational GC. Weakref (weakref.ref()) не увеличивает счётчик.

property

Декоратор для создания управляемых атрибутов (getter/setter/deleter):

class User:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not value:
            raise ValueError("Name cannot be empty")
        self._name = value

u = User("Alice")
u.name           # "Alice" — вызов getter
u.name = "Bob"  # вызов setter

raise from

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

Задаёт явную цепочку исключений (exception chaining):

try:
    result = int("abc")
except ValueError as e:
    raise RuntimeError("Failed to parse") from e
    # Traceback покажет: "The above exception was the direct cause..."

# Подавить цепочку:
except ValueError:
    raise RuntimeError("Failed") from None
    # Оригинальное исключение НЕ будет показано

Без from Python всё равно покажет оба исключения ("During handling of..."), но from делает связь явной и семантически правильной.

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)

Экосистема async Python

Частый провал: «не знает про uvloop, trio, httptools, orjson».
БиблиотекаЧто делает
uvloopЗамена стандартного event loop asyncio на основе libuv (в 2-4x быстрее). Uvicorn использует по умолчанию.
trioАльтернатива asyncio с более строгой structured concurrency. Используется вместо asyncio.
httptoolsБыстрый HTTP-парсер на C (используется в Uvicorn). Разбирает HTTP-запросы значительно быстрее стандартного.
orjsonБыстрый JSON-сериализатор на Rust. В 3-10x быстрее стандартного json. Поддерживает datetime, numpy.

SQLAlchemy & Alembic

SQLAlchemy — ORM + SQL toolkit для Python. Два стиля:

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase): pass

class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(100))

Alembic — инструмент миграций для SQLAlchemy (аналог Django migrations):

# Инициализация
alembic init migrations

# Создание миграции
alembic revision --autogenerate -m "add users table"

# Применение
alembic upgrade head

# Откат
alembic downgrade -1

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. Опасно в продакшене.

Модель Ричардсона (Richardson Maturity Model)

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

4 уровня зрелости REST API:

УровеньНазваниеОписание
0The Swamp of POXОдин endpoint, всё через POST (RPC-стиль)
1ResourcesОтдельные URI для ресурсов (/users, /orders), но один метод
2HTTP VerbsКорректные HTTP-методы (GET, POST, PUT, DELETE) + коды ответов
3HATEOASОтвет содержит ссылки на связанные действия/ресурсы

Большинство API в реальности на уровне 2. HATEOAS (уровень 3) встречается редко.

Cookie vs localStorage

CookielocalStorage
Размер~4 KB~5-10 MB
Отправляется на серверДа (автоматически с каждым запросом)Нет
Доступ из JSДа (если нет httpOnly)Да
Срок жизниExpires / Max-Age / SessionБессрочно (до ручной очистки)
БезопасностьhttpOnly, Secure, SameSiteУязвим к XSS
КогдаАутентификация (JWT refresh), сессииНастройки UI, кэш данных

Best practice: JWT access token — в памяти (JS variable); refresh token — в httpOnly cookie.

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;

VACUUM

Частый провал: «не знает про VACUUM».

PostgreSQL использует MVCC — при UPDATE/DELETE старая версия строки не удаляется сразу, а помечается как «мёртвая» (dead tuple). VACUUM освобождает это пространство.

VACUUMVACUUM FULL
Что делаетПомечает место мёртвых строк как свободное для повторного использованияПолностью перезаписывает таблицу, возвращает место на диск
БлокировкаНе блокирует чтение/записьExclusive lock — таблица недоступна
КогдаРегулярно (autovacuum)Редко, после массового DELETE

AUTOVACUUM — фоновый процесс PG, запускается автоматически. Также обновляет статистику для планировщика (ANALYZE).

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

РепликацияШардированиеПартиционирование
ЧтоКопии на другие серверыРазделение между серверамиРазделение внутри сервера
ЗачемОтказоустойчивость, 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.

KEYS vs SCAN

Частый провал: «не смог ответить про KEYS vs SCAN».
KEYS patternSCAN cursor [MATCH pattern]
БлокировкаБлокирует Redis на всё время выполненияНе блокирует — итеративный курсор
СложностьO(n) — обходит все ключи разомO(1) за итерацию, O(n) суммарно
ПродакшенЗапрещено (может повесить Redis на секунды)Безопасно
# ПЛОХО — никогда в продакшене:
KEYS user:*

# ХОРОШО — итеративно:
SCAN 0 MATCH user:* COUNT 100
# Возвращает (новый_курсор, [ключи]). Повторять до cursor == 0.

Аналоги: HSCAN, SSCAN, ZSCAN для Hash, Set, Sorted Set.

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 — классы, интерфейсы

DDD (Domain-Driven Design)

Подход к проектированию, где код организован вокруг бизнес-домена. Ключевые концепции:

КонцепцияОписание
EntityОбъект с уникальным ID (User, Order). Идентичность важнее атрибутов.
Value ObjectНеизменяемый объект без ID (Money, Address). Равенство по значению.
AggregateКластер сущностей с Aggregate Root — единственная точка доступа.
RepositoryАбстракция доступа к данным (см. паттерны).
Domain ServiceБизнес-логика, не принадлежащая конкретной сущности.
Bounded ContextГраница, внутри которой модель единообразна. Разные контексты могут иметь разные модели User.
Ubiquitous LanguageЕдиный язык между разработчиками и бизнесом.

Виды архитектур

АрхитектураОписание
МонолитЕдиное приложение, единый деплой
МикросервисыНезависимые сервисы, своя БД, свой деплой
SOAService-Oriented Architecture — предшественник микросервисов, общая шина (ESB)
Event-DrivenКомпоненты общаются через события (Kafka, RabbitMQ)
Hexagonal (Ports & Adapters)Бизнес-логика в центре, внешний мир через порты/адаптеры
Clean ArchitectureЗависимости направлены внутрь: Entities ← Use Cases ← Adapters ← Frameworks

Микросервисы 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

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

Repository

Абстрагирует доступ к данным. Бизнес-логика работает с интерфейсом, а не с конкретной БД:

class UserRepository(Protocol):
    def get_by_id(self, id: int) -> User: ...
    def save(self, user: User) -> None: ...

class PostgresUserRepo:
    def __init__(self, session: Session):
        self.session = session
    def get_by_id(self, id: int) -> User:
        return self.session.get(User, id)
    def save(self, user: User):
        self.session.add(user); self.session.flush()

Service Layer

Слой между API-хэндлерами и репозиторием. Содержит бизнес-логику и оркестрацию:

class OrderService:
    def __init__(self, repo: OrderRepository, notifier: Notifier):
        self.repo = repo
        self.notifier = notifier

    def place_order(self, user_id: int, items: list) -> Order:
        order = Order(user_id=user_id, items=items)
        self.repo.save(order)
        self.notifier.send(user_id, f"Order {order.id} placed")
        return order

Позволяет переиспользовать бизнес-логику из разных точек входа (API, CLI, Celery tasks).

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).

OAuth 2.0

Протокол авторизации для доступа к ресурсам третьих сторон без передачи пароля. Роли:

Основные flow: Authorization Code (веб), Client Credentials (server-to-server), PKCE (мобильные/SPA).

CORS

Cross-Origin Resource Sharing — механизм браузера, ограничивающий запросы между разными origin (домен + порт + протокол).

# FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware,
    allow_origins=["https://frontend.com"],
    allow_methods=["*"],
    allow_headers=["*"],
    allow_credentials=True)

DDoS и Rate Limiting

DDoS — распределённая атака, перегружающая сервер огромным числом запросов. Защита:

# Простой rate limiter на Redis
def is_allowed(redis, user_id, limit=100, window=60):
    key = f"rate:{user_id}"
    current = redis.incr(key)
    if current == 1:
        redis.expire(key, window)
    return current <= limit

24 UNIX & Git

Linux

Git (Senior level)