junior: база, с которой начинают
Объясни, что такое идемпотентность HTTP-метода. Какие из стандартных методов идемпотентны, а какие нет, и почему POST — нет?
как ответить Идемпотентный метод — это когда N одинаковых запросов подряд приводят ресурс в то же состояние, что и один запрос (ответ при этом может отличаться, например код 404 после первого DELETE). GET, PUT, DELETE, HEAD, OPTIONS идемпотентны; POST — нет, потому что каждый вызов обычно создаёт новую сущность; PATCH тоже не гарантированно идемпотентен, если в теле операция вроде «увеличить счётчик на 1», а не полная замена поля.
разбор Идемпотентность — это про состояние сервера, а не про сетевой эффект: два одинаковых DELETE вернут 200 и 404, но ресурса после обоих не будет — это и есть идемпотентность. Отдельно стоит safe-методы (GET, HEAD, OPTIONS) — они вообще не должны менять состояние, что сильнее идемпотентности.
Типичная ловушка — путать идемпотентность с «безопасностью» метода или считать PUT идемпотентным автоматически: PUT идемпотентен только если это полная замена ресурса тем же телом, а не относительное изменение.
Добивают follow-up'ом: как обеспечить идемпотентность неидемпотентного по природе POST при сетевых ретраях (например, повторная отправка платежа из-за таймаута). Ответ — клиент генерирует Idempotency-Key, сервер по этому ключу дедуплицирует запросы и возвращает сохранённый результат первого выполнения вместо повторного создания сущности.
чтобы прозвучать сильнее Подними до middle, если свяжешь это с at-least-once доставкой: клиентские ретраи при таймауте — обычная причина, почему платёжные и вебхук-эндпоинты обязаны быть идемпотентны даже при формально неидемпотентном методе.
python-backend-001 · junior · high Клиент получил от API 401 и 403 — в чём разница и когда какой отдавать? А 400 против 422?
как ответить 401 Unauthorized значит «сервер не знает, кто ты» — нет валидных credentials, нужна (пере)аутентификация. 403 Forbidden значит «сервер знает, кто ты, но у тебя нет прав» — переавторизация не поможет. 400 Bad Request — запрос синтаксически или структурно битый (невалидный JSON, нет обязательного поля); 422 Unprocessable Entity — запрос корректно распарсился, но не проходит бизнес-валидацию (email уже занят, дата в прошлом).
разбор Частая ошибка — реализовать «401 при истёкшем токене» без заголовка WWW-Authenticate, что формально нарушает спеку и ломает автоматическую переавторизацию некоторых клиентов.
422 не входит в исходный HTTP/1.1, пришёл из WebDAV (RFC 4918), но стал де-факто стандартом в REST API для семантических ошибок валидации — многие фреймворки возвращают его по умолчанию для form/schema validation.
Подвох на собесе: для приватного ресурса, к которому у юзера нет доступа, иногда сознательно отдают 404 вместо 403 — чтобы не подтверждать сам факт существования ресурса неавторизованному пользователю (например, чужой приватный документ).
Добивают уточнением формата тела ошибки: код ошибки машиночитаемый (INVALID_EMAIL) плюс человекочитаемое сообщение, а не голый текст в 400.
чтобы прозвучать сильнее Прозвучишь на middle, если сам вспомнишь про 404-вместо-403 как способ не палить существование ресурса — это регулярный follow-up на этот вопрос.
python-backend-002 · junior · high Чем принципиально отличаются Django, Flask и FastAPI, и как ты выбираешь между ними для нового проекта?
как ответить Django — полнофункциональный batteries-included фреймворк с ORM, admin-панелью и жёсткими конвенциями, годится для CRUD-приложений с быстрым стартом. Flask — минималистичный синхронный WSGI-микрофреймворк, даёт свободу собрать стек самому и годится для небольших сервисов. FastAPI — async-first фреймворк на ASGI с автоматической валидацией через Pydantic и OpenAPI-документацией из коробки — выбор для высоконагруженных API и I/O-bound сервисов.
полный разбор и проверка ответа арбитром — в приложении
python-backend-003 · junior · high У тебя в Django/SQLAlchemy эндпоинт выводит список постов вместе с автором каждого поста, и на проде он стал ощутимо тормозить с ростом данных. Что происходит и как это чинить?
как ответить N+1 запрос — для списка из N объектов ORM делает 1 запрос на саму выборку плюс ещё N отдельных запросов на связанные объекты в момент обращения к ним в цикле (например, author.name). Лечится eager loading: select_related для to-one связей (один JOIN) и prefetch_related для to-many (отдельный запрос с IN и склейка в Python); в SQLAlchemy аналоги — joinedload и selectinload. Диагностируется по числу SQL-запросов в логах или через django-debug-toolbar — оно растёт линейно с размером выборки.
полный разбор и проверка ответа арбитром — в приложении
python-backend-004 · junior · high У нас в проекте на границе API везде pydantic. Расскажи, зачем он нужен — чем валидация через pydantic лучше ручных if-проверок или голых dataclass?
как ответить Pydantic декларативно описывает контракт данных через аннотации типов и валидаторы: на входе автоматически парсит и коерсит значения, а при ошибке возвращает структурированный список всех невалидных полей сразу, а не падает на первом if. Та же модель используется и для сериализации ответа — вход и выход описаны одним источником правды, из которого потом строится OpenAPI-схема. Ручные проверки и голые dataclass на это не масштабируются: логика валидации дублируется по хендлерам, ошибки в разном формате, и никакой самодокументации нет.
полный разбор и проверка ответа арбитром — в приложении
python-backend-005 · junior · high Чем валидация данных на границе API должна отличаться от проверки бизнес-правил внутри сервиса? Что проверяешь в pydantic-модели запроса, а что уже в use-case?
как ответить На границе (в pydantic-модели) проверяется форма: типы, обязательность полей, диапазоны, формат email или UUID — всё, что не зависит от текущего состояния системы и не требует похода в базу. Бизнес-правила вроде «email уже занят» или «хватает ли кредитов» — это семантика, завязанная на состояние данных, и она живёт в доменном слое, а не в модели запроса. Смешивать их плохо: если валидатор запроса лезет в БД, парсинг входа превращается в скрытый side-effect и ломает тестируемость.
полный разбор и проверка ответа арбитром — в приложении
python-backend-006 · junior · high Чем аутентификация на сессиях отличается от JWT, и где на фронте ты будешь хранить токен?
как ответить Сессии — стейтфул: сервер хранит запись (в Redis/БД) и отдаёт клиенту opaque session id в cookie; отозвать доступ — просто удалить запись. JWT — стейтлес: подписанный токен со всеми claim'ами лежит у клиента, сервер ничего не хранит и не ходит в БД на каждый запрос, но досрочный отзыв до истечения TTL требует отдельного механизма (blacklist, короткий TTL + refresh). На фронте токен храним в httpOnly Secure cookie с SameSite, а не в localStorage — иначе он читается любым инжектированным JS при XSS.
полный разбор и проверка ответа арбитром — в приложении
python-backend-007 · junior · high Что такое rate limiting и какими способами его обычно реализуют?
как ответить Rate limiting ограничивает число запросов от одного клиента (по IP, user id или API-ключу) за окно времени, чтобы защититься от abuse, брутфорса и случайных штормов трафика от одного источника. Базовые алгоритмы — fixed window (счётчик на интервал, простой, но даёт всплеск на границе окна), sliding window и token bucket (плавнее, допускает короткие всплески в рамках бюджета). На практике счётчик держат в Redis через INCR + EXPIRE или готовую Lua-обвязку, а при превышении отдают 429 с заголовком Retry-After.
полный разбор и проверка ответа арбитром — в приложении
python-backend-008 · junior · high Как ты проектируешь URL для REST API — например, для действий вроде «поставить лайк комментарию» или «архивировать заказ»? Как выбираешь между вложенным ресурсом и отдельным action-эндпоинтом?
как ответить Ресурсы в пути — существительные во множественном числе, действие кодируется HTTP-методом, а не глаголом: лайк — это POST /comments/{id}/likes, снять лайк — DELETE /comments/{id}/likes/{userId}. Для операций, которые не сводятся к CRUD над ресурсом (архивировать, одобрить, отправить), прагматично завести под-ресурс-действие вроде POST /orders/{id}/archive — формально это не чистый REST, но понятнее и безопаснее, чем протаскивать это через PATCH {status: 'archived'} с магическим полем.
полный разбор и проверка ответа арбитром — в приложении
python-backend-009 · junior · medium middle: где отделяют уверенных
Расскажи про атрибуты cookie — HttpOnly, Secure, SameSite. Зачем они нужны и как SameSite защищает от CSRF?
как ответить HttpOnly запрещает доступ к куке из JS через document.cookie, так что XSS не может её напрямую утащить. Secure — кука уходит только по HTTPS. SameSite ограничивает отправку куки в кросс-сайтовых запросах: Strict вообще не шлёт куку при переходе с другого сайта, Lax (дефолт в современных браузерах) шлёт её на top-level GET-навигации, но не на кросс-сайтовых POST/fetch — это и закрывает классический CSRF через автосабмит формы с чужого сайта; None снимает ограничение, но требует Secure и нужен для осознанной кросс-сайтовой отправки (виджеты, iframe-интеграции).
полный разбор и проверка ответа арбитром — в приложении
python-backend-013 · middle · high Как Django или Flask реально обслуживается в проде — что делает gunicorn, что такое воркеры и сколько их нужно?
как ответить Перед WSGI-приложением в проде ставят gunicorn как process manager позади nginx (nginx держит TLS и статику, gunicorn — приложение). Gunicorn поднимает N процессов-воркеров, каждый — независимый интерпретатор со своим GIL, обрабатывающий один запрос синхронно. Число sync-воркеров обычно считают по формуле (2×CPU)+1; для I/O-bound нагрузки берут worker-class gevent/eventlet или gthread с несколькими потоками на воркер.
полный разбор и проверка ответа арбитром — в приложении
python-backend-014 · middle · high Объясни, как работает atomic-блок транзакции в Django/SQLAlchemy, и приведи пример, где забыли обернуть код в транзакцию и это привело к багу.
как ответить atomic (Django transaction.atomic, SQLAlchemy Session.begin) оборачивает блок кода в одну БД-транзакцию: все запросы внутри коммитятся вместе или откатываются вместе при необработанном исключении. Вложенные atomic-блоки в Django создают savepoint, а не отдельную транзакцию — откат внутреннего блока откатывает только до этого savepoint. Без atomic частичная запись — например, списали деньги, но не создали запись о заказе — при падении между двумя запросами оставляет БД в несогласованном состоянии.
полный разбор и проверка ответа арбитром — в приложении
python-backend-015 · middle · high Как у тебя в проекте устроена единообразная обработка ошибок API — что клиент получает в теле ответа при ошибке валидации, 404 или 500? Почему это должно быть единообразно?
как ответить У всех ошибок должен быть один формат-конверт — код ошибки, человекочитаемое сообщение и для валидации список проблемных полей с причиной — независимо от того, прилетела ли она из pydantic, из доменного исключения или из необработанного краша. Достигается это глобальным exception-хендлером на уровне фреймворка, который мапит иерархию исключений на HTTP-статус и этот конверт, а не try/except в каждом отдельном роуте. Отдельно важно не течь внутренние детали — стектрейс, текст SQL-ошибки — наружу на проде, но полностью логировать их на сервере.
полный разбор и проверка ответа арбитром — в приложении
python-backend-016 · middle · high Как ты кешируешь данные в Redis в проде и как решаешь проблему инвалидации кеша?
как ответить Стандартный паттерн — cache-aside: при чтении сперва идём в Redis, промах — читаем из БД и кладём в кеш с TTL. Инвалидация — либо ждём истечения TTL (простая, но допускает stale-данные внутри окна), либо явно удаляем/обновляем ключ синхронно с записью в БД. Против cache stampede (все запросы разом лезут в БД после протухания горячего ключа) — jitter в TTL и лок/single-flight на пересчёт значения.
полный разбор и проверка ответа арбитром — в приложении
python-backend-017 · middle · high Лента сортируется по created_at DESC, id DESC. Почему запрос становится медленным на дальних страницах и может давать пропуски или повторы при параллельных изменениях данных? Как перейти на курсорную пагинацию, что положить в курсор и какой индекс создать?
SELECT id, created_at, title
FROM posts
ORDER BY created_at DESC, id DESC
LIMIT 50 OFFSET 500000;
как ответить PostgreSQL всё равно вычисляет пропущенные OFFSET строк, поэтому стоимость растёт с номером страницы. Между запросами набор сдвигается: новая строка перед уже прочитанной позицией может привести к повтору. Курсор хранит последнюю пару (created_at, id), следующая страница выбирается условием WHERE (created_at, id) < ($1, $2), а порядок и LIMIT остаются прежними. Нужен индекс (created_at DESC, id DESC).
полный разбор и проверка ответа арбитром — в приложении
python-backend-027 · middle · high