0% found this document useful (0 votes)
3 views20 pages

FastAPI & REST API Interview Cookbook

Uploaded by

sainilakantan
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views20 pages

FastAPI & REST API Interview Cookbook

Uploaded by

sainilakantan
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

FastAPI & REST API Interview Cookbook

Role: Python Full Stack AI Developer Intern — Future Transformation, Bengaluru Focus:
Backend — REST APIs + FastAPI (with AI/ML integration angles from the JD)

0. How they'll likely test you


The JD + your submitted take-home tell you exactly where questions will come from:

1.​ Your take-home itself (highest probability) — JWT + RBAC, FAISS +


sentence-transformers, FastAPI + MySQL. Expect "walk me through it" + "why did
you choose X" + "what breaks at scale".
2.​ REST fundamentals — methods, status codes, idempotency, 401 vs 403, PUT vs
PATCH.
3.​ FastAPI specifics — async vs sync, Pydantic, Depends, vs Flask/Django.
4.​ AI/ML in production APIs — model loading, real-time vs batch (it's literally in the
JD).
5.​ One practical — "design an endpoint for X" or live-debug a snippet.

Strategy: answer in 2–3 sentences, then offer your project as proof. "...and that's exactly how
I handled uploads in MeetingMind."

1. REST API Fundamentals


1.1 What is REST?

An architectural style for APIs over HTTP. Core constraints: client–server, stateless (each
request carries everything needed — no server-side session), cacheable, uniform
interface (resources identified by URIs, manipulated via standard methods), layered
system.

One-liner: "REST treats everything as a resource with a URI, manipulated


through standard HTTP verbs, with no server-side session state."

1.2 HTTP methods — semantics + idempotency

Method Use Safe Idempotent?


?
GET Read a resource Yes Yes

POST Create / trigger action No No

PUT Full replace of a No Yes


resource

PATCH Partial update No Not


guaranteed

DELETE Remove No Yes

Idempotent = repeating the same request leaves the server in the same state (DELETE
/tasks/5 twice → still deleted; second call returns 404 or 204, state unchanged). POST twice
→ two resources, hence not idempotent.

PUT vs PATCH: PUT sends the complete representation (missing fields are wiped/reset);
PATCH sends only the fields to change.

PUT vs POST: PUT targets a known URI (PUT /tasks/5); POST targets a collection and
the server assigns the ID (POST /tasks).

1.3 Status codes you must know cold

Cod Meaning When


e

200 OK Successful GET/PUT/PATCH

201 Created Successful POST (return the new resource + Location


header)

202 Accepted Request accepted, processing async (batch jobs)


204 No Content Successful DELETE

400 Bad Request Malformed input

401 Unauthorized Not authenticated (missing/invalid token)

403 Forbidden Authenticated but not allowed (RBAC denial)

404 Not Found Resource doesn't exist

409 Conflict Duplicate (e.g., email already registered)

422 Unprocessable FastAPI's Pydantic validation failure


Entity

429 Too Many Requests Rate limited

500 Internal Server Error Unhandled exception

503 Service Unavailable Dependency down

401 vs 403 is a near-guaranteed question: 401 = "who are you?", 403 = "I
know who you are; you can't do this." In your take-home: bad JWT → 401,
valid JWT but wrong role → 403.

1.4 Resource design rules

●​ Plural nouns, not verbs: GET /api/v1/tasks, not /getTasks.


●​ Hierarchy for ownership: GET /users/{id}/tasks (keep nesting to one level).
●​ Filtering/sorting/pagination via query params: GET
/tasks?status=open&limit=20&offset=40.
●​ Actions that don't map to CRUD → sub-resource verb sparingly: POST
/meetings/{id}/reprocess.

1.5 Versioning, pagination, rate limiting

●​ Versioning: URI path (/api/v1/...) is most common and explicit; header-based


is cleaner URLs but harder to debug.
●​ Pagination: offset/limit — simple, but slow on deep pages and unstable if rows are
inserted; cursor-based (?after=<id>) — stable and fast for feeds.
●​ Rate limiting: token bucket per API key/IP, return 429 + Retry-After. In FastAPI:
middleware or slowapi.

1.6 Statelessness & auth

Stateless means the server keeps no session — so auth state must travel with each request.
That's exactly why JWT fits REST: the token is the session, signed so the server can trust it
without a lookup.

1.7 REST vs alternatives (one-liners)

●​ GraphQL: client specifies the shape of data; solves over/under-fetching; adds server
complexity and caching difficulty.
●​ gRPC: binary protobuf over HTTP/2; great for internal microservice-to-microservice
calls; not browser-friendly.
●​ WebSocket: persistent bidirectional channel; for server-push (you used it for live
pipeline progress in MeetingMind).

2. FastAPI Core
2.1 What is FastAPI / why use it?

A modern Python web framework built on Starlette (ASGI, async) + Pydantic (type-driven
validation). You get: automatic request validation, automatic OpenAPI/Swagger docs at
/docs, dependency injection, and async-native performance.

2.2 FastAPI vs Flask vs Django (the JD lists all three — this WILL come
up)

FastAPI Flask Django


Concurrenc Async-native (ASGI) Sync (WSGI) by Sync core (ASGI possible)
y default

Validation Built-in via Pydantic Manual / Forms/DRF serializers


extensions

Docs Auto OpenAPI Manual Manual / DRF

Best for APIs, ML serving, Small apps, Full products (ORM, admin,
I/O-heavy simplicity auth built in)

Crisp answer: "Flask is minimal and sync; Django is batteries-included for full
web apps; FastAPI is async-first with type-driven validation and auto docs —
best fit for API backends serving ML models, which is why I used it in both my
projects."

2.3 Path, query, and body params

from fastapi import FastAPI

from pydantic import BaseModel, Field

app = FastAPI()

class TaskCreate(BaseModel):

title: str = Field(min_length=1, max_length=200)

priority: int = Field(default=1, ge=1, le=5)

@[Link]("/tasks", status_code=201)

def create_task(task: TaskCreate): # body (Pydantic)

...
@[Link]("/tasks/{task_id}")

def get_task(task_id: int, verbose: bool = False): # path + query

...

Type hints drive parsing: wrong types → automatic 422 with a structured error body. You
write zero validation boilerplate.

2.4 Pydantic — validation in, filtering out

●​ Request: invalid payloads never reach your handler.


●​ Response: response_model= filters the output — the canonical example is never
leaking hashed_password:

class UserOut(BaseModel):

id: int

email: str # no password field → it's stripped from the response

@[Link]("/users/me", response_model=UserOut)

def me(current_user = Depends(get_current_user)):

return current_user # even if dict contains hashed_password, it's filtered

2.5 Dependency Injection (Depends) — FastAPI's killer feature

Reusable, declarative components injected per-request. Used for DB connections, auth,


pagination params, RBAC.

from fastapi import Depends

def get_db():

conn = [Link]()

try:

yield conn # yield-dependency = setup + guaranteed teardown

finally:
[Link](conn)

@[Link]("/tasks")

def list_tasks(db = Depends(get_db), user = Depends(get_current_user)):

...

Key facts: dependencies can depend on other dependencies (chains), results are cached
within a single request, and yield gives you try/finally cleanup. Also the #1 testing lever
(see §7).

2.6 async def vs def — know this precisely

●​ async def endpoint → runs on the event loop. Must only await non-blocking I/O.
A blocking call (e.g., [Link], heavy model inference, sync DB driver) inside
async def freezes the entire server.
●​ Plain def endpoint → FastAPI runs it in an external threadpool, so blocking code is
safe and doesn't stall other requests.
●​ CPU-bound work (Whisper transcription, embedding generation) → threadpool
([Link].run_in_threadpool), process pool, or a background
worker — never directly in async def.

Your proof: "MeetingMind's transcription is CPU-bound faster-whisper, so it runs


as a background task off the request path; the API handlers stay responsive."

2.7 Project structure: routers

# api/[Link]

from fastapi import APIRouter

router = APIRouter(prefix="/api/tasks", tags=["tasks"])

# [Link]

app.include_router(tasks_router)

app.include_router(auth_router)
Both your projects follow this: [Link] is thin (CORS, routers, startup, exception handlers);
each domain gets its own router module.

2.8 Middleware & CORS

CORS is browser-enforced: a page on localhost:5173 (Vite) calling localhost:8000


is cross-origin, so the browser blocks responses unless the server sends
Access-Control-Allow-Origin.

from [Link] import CORSMiddleware

app.add_middleware(

CORSMiddleware,

allow_origins=[CORS_ORIGIN], # exact origin, not "*", when credentials are used

allow_credentials=True,

allow_methods=["*"],

allow_headers=["*"],

2.9 Error handling

from fastapi import HTTPException

@[Link]("/tasks/{task_id}")

def get_task(task_id: int):

task = fetch(task_id)

if not task:

raise HTTPException(status_code=404, detail="Task not found")

return task

@app.exception_handler(Exception)

async def global_handler(request, exc):


[Link]("Unhandled error on %s %s", [Link], [Link])

return JSONResponse(status_code=500, content={"detail": "Internal server error"})

Talking point: "I add a global exception handler so clients get structured JSON,
never a raw traceback — tracebacks leak internals and are a security issue."

2.10 Background tasks vs task queues

from fastapi import BackgroundTasks

@[Link]("/meetings", status_code=201)

def upload(file: UploadFile, background_tasks: BackgroundTasks):

meeting = create_meeting(...)

background_tasks.add_task(process_meeting, meeting["id"])

return meeting # respond immediately; processing continues after response

●​ BackgroundTasks: same process, fire-after-response. Good for minutes-scale


work in a small system.
●​ Limit: if the process restarts, in-flight jobs are lost; no retries; no horizontal scaling of
workers.
●​ At scale → Celery/RQ + Redis: durable queue, retries, separate worker fleet.

Strong answer pattern: "I used BackgroundTasks deliberately —


single-container deployment, zero-cost constraint. I'd state the upgrade path to
Celery + Redis when job durability matters."

2.11 Startup/lifespan — where ML models belong

from contextlib import asynccontextmanager

@asynccontextmanager

async def lifespan(app: FastAPI):

[Link] = SentenceTransformer("all-MiniLM-L6-v2") # load ONCE

init_db()
yield

# cleanup here

app = FastAPI(lifespan=lifespan)

Loading a model per-request is the classic anti-pattern: load at startup (or lazy singleton)
and reuse.

2.12 Auto docs

FastAPI generates an OpenAPI schema → Swagger UI at /docs, ReDoc at /redoc. Free,


always in sync with code — great line: "my API documentation can't drift from the
implementation."

3. Auth: JWT + RBAC (your take-home stack — expect


a deep dive)
3.1 JWT anatomy

[Link] — three base64url parts.

●​ Header: algorithm (HS256 = HMAC-SHA256).


●​ Payload (claims): sub (user id), role, exp (expiry), iat.
●​ Signature: HMAC(secret, header + "." + payload).

The server verifies the signature instead of looking up a session → stateless auth. Anyone
can read a JWT (it's encoded, not encrypted) — never put secrets in the payload. Tampering
breaks the signature.

3.2 The full flow in FastAPI

from [Link] import OAuth2PasswordBearer, OAuth2PasswordRequestForm

from jose import jwt, JWTError

from [Link] import CryptContext

from datetime import datetime, timedelta, timezone


pwd = CryptContext(schemes=["bcrypt"])

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")

@[Link]("/auth/login")

def login(form: OAuth2PasswordRequestForm = Depends()):

user = get_user_by_email([Link])

if not user or not [Link]([Link], user["hashed_password"]):

raise HTTPException(401, "Invalid credentials")

token = [Link](

{"sub": str(user["id"]), "role": user["role"],

"exp": [Link]([Link]) + timedelta(minutes=30)},

SECRET_KEY, algorithm="HS256",

return {"access_token": token, "token_type": "bearer"}

def get_current_user(token: str = Depends(oauth2_scheme)):

try:

payload = [Link](token, SECRET_KEY, algorithms=["HS256"])

except JWTError:

raise HTTPException(401, "Invalid or expired token")

user = get_user_by_id(int(payload["sub"]))

if not user:

raise HTTPException(401, "User not found")

return user

3.3 RBAC as a dependency factory


def require_role(*roles: str):

def checker(user = Depends(get_current_user)):

if user["role"] not in roles:

raise HTTPException(403, "Insufficient permissions") # 403, not 401

return user

return checker

@[Link]("/users/{user_id}", status_code=204)

def delete_user(user_id: int, admin = Depends(require_role("admin"))):

...

"Authentication is one dependency, authorization is a dependency factory


layered on top — each protected route declares the roles it needs."

3.4 Security checklist (rapid-fire material)

●​ Passwords: bcrypt hash (salted, slow by design) — never plaintext, never reversible
encryption.
●​ SQL injection: parameterized queries only (%s placeholders — your MeetingMind
rule: "zero string interpolation").
●​ Secrets: env vars / .env, never committed.
●​ Token expiry short (15–60 min); refresh tokens for long sessions.
●​ Logout with stateless JWT: client discards token; true revocation needs a blacklist or
short expiry — honest tradeoff to state.
●​ HTTPS everywhere; CORS locked to known origins.
●​ Don't leak whether email or password was wrong (uniform 401 message).

4. Database Layer
4.1 Raw SQL vs ORM — your strongest differentiator

You chose raw SQL (psycopg3 in MeetingMind, MySQL connector in the take-home).
Defend it:

●​ Why raw SQL: full visibility into every query, no hidden N+1s, learn what the ORM
abstracts, trivial to optimize, fewer dependencies.
●​ What you give up: migrations tooling (Alembic), model classes, portability.
●​ Mitigations you actually used: a single [Link] data-access layer,
idempotent [Link] migration script, dict_row row factory, %s
parameterization everywhere.

"I'd use SQLAlchemy on a team for migrations and shared conventions — I


chose raw SQL deliberately to own the query layer and it made debugging
trivial."

4.2 Must-know concepts

●​ Connection pooling: opening a DB connection is expensive; a pool reuses N warm


connections across requests (psycopg_pool / SQLAlchemy pool). One connection
per request without pooling collapses under load.
●​ Transactions / ACID: Atomicity, Consistency, Isolation, Durability. Multi-statement
writes (create meeting + insert segments) wrap in one transaction so partial failure
rolls back.
●​ N+1 problem: 1 query for a list + 1 query per item. Fix: JOIN or a single IN (...)
query.
●​ Indexes: B-tree on columns you filter/join/sort by (status, foreign keys). Cost:
slower writes, more storage — don't index everything.
●​ SQL injection: f"WHERE id = {user_input}" is the vulnerability;
[Link]("WHERE id = %s", (user_input,)) is the fix. The driver
escapes values; query structure can't be altered.

4.3 PostgreSQL vs MySQL vs MongoDB (JD lists all three)

●​ PostgreSQL: richest feature set — JSONB, arrays, full-text search, strong standards
compliance. Your MeetingMind choice (JSONB for topics).
●​ MySQL: ubiquitous, fast for read-heavy web workloads. Your take-home choice.
●​ MongoDB: document store, flexible schema, horizontal scaling — fits
unstructured/rapidly-evolving data; you lose joins and strict relational integrity.

Pick rule: "Relational by default; document store when the data is genuinely
schemaless or denormalized reads dominate."

5. AI/ML Inside APIs (JD: "real-time or batch


processing")
5.1 The two serving patterns

Real-time (synchronous): request → inference → response. Requirements: model


pre-loaded (lifespan/singleton), inference fast enough (<~2s), CPU-bound inference pushed
to threadpool so the event loop stays free.
Batch (asynchronous): — you built this end-to-end in MeetingMind

1.​ POST /meetings accepts the file → creates a DB row with status="queued" →
returns 201 immediately with the job/meeting id.
2.​ Background worker advances status: transcribing → extracting →
complete (or error).
3.​ Client either polls GET /meetings/{id} or subscribes to WebSocket
/ws/meetings/{id} for pushed progress events.

This is the single best story you have for the JD's AI-integration bullet. Rehearse
it as a 60-second narrative.

5.2 LLM API integration patterns (MeetingMind)

●​ Timeouts + retries on every external call.


●​ Provider fallback: Groq (Llama 3.3 70B) primary → Gemini Flash on
failure/rate-limit — resilience under free-tier quotas.
●​ Schema-validated outputs: every agent's JSON is validated; a Reviewer agent
re-runs weak extractions. "Never trust raw LLM output into your DB."

5.3 Embeddings + FAISS endpoint (your take-home — be ready to


whiteboard this)

from sentence_transformers import SentenceTransformer

import faiss, numpy as np

model = SentenceTransformer("all-MiniLM-L6-v2") # 384-dim, CPU-friendly

index = [Link](384) # inner product

# normalize vectors → inner product == cosine similarity

def add_docs(texts: list[str]):

vecs = [Link](texts, normalize_embeddings=True)

[Link]([Link](vecs, dtype="float32"))

@[Link]("/search")

def search(q: str, k: int = 5, user = Depends(get_current_user)):


qv = [Link]([q], normalize_embeddings=True)

scores, ids = [Link]([Link](qv, dtype="float32"), k)

return hydrate_from_db(ids[0], scores[0]) # FAISS stores vectors; metadata lives in


MySQL

Key points to articulate:

●​ Why local embeddings: the brief banned external LLM APIs → privacy + zero cost
+ no network latency; all-MiniLM-L6-v2 runs comfortably on CPU.
●​ FAISS stores vectors only — IDs map back to MySQL rows for content/metadata.
●​ IndexFlatIP/L2 = exact brute-force search; fine to ~100k vectors. At millions → IVF
or HNSW (approximate, sub-linear).
●​ Persistence: faiss.write_index() to disk, reload at startup; or rebuild from DB
on boot.
●​ Why FAISS over pgvector/Chroma: zero extra infrastructure, in-process, fastest
path for the assignment; pgvector wins when you want vectors + relational data
transactionally in one store.

5.4 File uploads

from fastapi import UploadFile, File

@[Link]("/meetings", status_code=201)

def upload(file: UploadFile = File(...)):

if file.content_type not in {"audio/mpeg", "audio/wav", "audio/x-m4a"}:

raise HTTPException(415, "Unsupported media type")

dest = UPLOAD_DIR / f"{uuid4()}_{[Link]}" # uuid prefix → no collisions

...

UploadFile spools to a temp file (memory-safe for large files), unlike bytes which loads
everything into RAM.

6. Testing (you have 93 passing tests — use this)


from [Link] import TestClient

from [Link] import app

client = TestClient(app)

def test_create_task_requires_auth():

assert [Link]("/api/tasks", json={"title": "x"}).status_code == 401

def test_get_task_not_found(auth_headers):

assert [Link]("/api/tasks/9999", headers=auth_headers).status_code == 404

Dependency overrides — the FastAPI-native way to mock:

app.dependency_overrides[get_current_user] = lambda: {"id": 1, "role": "admin"}

app.dependency_overrides[get_db] = get_test_db

Talking points: test pyramid (unit query functions → API integration via TestClient → a few
end-to-end), test the failure paths (401/403/404/422), CI runs the suite on every push.

7. Deployment
●​ Server: uvicorn (ASGI). Production: multiple workers — uvicorn --workers 4
or gunicorn -k [Link].
●​ Docker: slim Python base, copy [Link] first (layer caching), then
code; non-root user; config via env vars (12-factor).
●​ Your HF Spaces pattern (nice story): one container serves both API and the built
Vite SPA — StaticFiles mount for /assets + a catch-all route returning
[Link] for client-side routing, with API routes registered first so they always
win.
●​ Health endpoint: GET /api/health that genuinely pings the DB — load balancers
and CI smoke tests depend on it.
●​ CI/CD: push → lint + pytest → build image → deploy. You have this on MeetingMind.
8. Rapid-fire Q&A (say these in ≤3 sentences each)
1.​ What makes an API RESTful? Resources via URIs, standard HTTP verbs,
statelessness, uniform interface.
2.​ Idempotency? Repeating a request doesn't change server state further.
GET/PUT/DELETE yes, POST no.
3.​ 401 vs 403? 401 = not authenticated; 403 = authenticated but not permitted.
4.​ PUT vs PATCH? Full replace vs partial update.
5.​ Why FastAPI over Flask? Async ASGI, Pydantic validation, auto OpenAPI docs, DI
built in.
6.​ What's ASGI? Async successor to WSGI — lets one worker handle many concurrent
I/O-bound requests and supports WebSockets.
7.​ async def vs def in FastAPI? async def runs on the event loop (never block it);
def runs in a threadpool (blocking is safe).
8.​ What is Depends? Per-request dependency injection — auth, DB sessions, shared
params; cached within the request; yield gives teardown.
9.​ What does Pydantic do? Type-driven parsing/validation of requests (auto 422) and
response filtering via response_model.
10.​How do you return 201? @[Link](..., status_code=201) and return the
created resource.
11.​What is a JWT? Signed [Link] token; server verifies the
signature instead of storing sessions; readable but tamper-proof.
12.​Where do you store the JWT secret? Environment variable; rotate if leaked; never
in code.
13.​How do you hash passwords? bcrypt via passlib — salted and deliberately slow.
14.​How do you prevent SQL injection? Parameterized queries only; never interpolate
user input into SQL strings.
15.​What's connection pooling? Reusing warm DB connections across requests
because opening one is expensive.
16.​N+1 query problem? A list query followed by one query per item; fix with JOINs or
batched IN queries.
17.​When MongoDB over MySQL? Schemaless/rapidly-evolving documents,
denormalized read patterns; otherwise relational by default.
18.​How do you serve an ML model in an API? Load once at startup
(lifespan/singleton), run CPU-bound inference off the event loop, return predictions;
batch jobs go through a queue + status endpoint.
19.​Real-time vs batch ML processing? Sync inference within the request vs
accept-job → 201/202 + id → background processing → poll or push status.
20.​How do you handle long-running tasks? BackgroundTasks for simple cases;
Celery/RQ + Redis for durability, retries, and scale.
21.​What is CORS? Browser security policy restricting cross-origin requests; the server
opts in via Access-Control-Allow-Origin.
22.​What's a 422 in FastAPI? Pydantic validation failed — body/params didn't match
declared types/constraints.
23.​How do you version an API? Path versioning (/api/v1/) most commonly; keep
old versions until clients migrate.
24.​Offset vs cursor pagination? Offset is simple but unstable/slow at depth; cursor is
stable and fast for feeds.
25.​How do you test FastAPI? TestClient + pytest; mock auth/DB with
app.dependency_overrides; cover failure paths.
26.​What is OpenAPI? A machine-readable spec of your API; FastAPI generates it
automatically → Swagger UI.
27.​Microservices vs monolith? Independent deployability and scaling vs operational
overhead; start modular-monolith, split on real pain.
28.​What is FAISS? Facebook's vector similarity-search library; stores embeddings,
returns nearest neighbors; exact (Flat) or approximate (IVF/HNSW) indexes.
29.​Why local embeddings over an API? Privacy, zero marginal cost, no network
latency, no rate limits — at the cost of model size/quality ceiling.
30.​How does your WebSocket endpoint work? Client connects per-meeting; an
in-process event bus broadcasts pipeline progress; background threads bridge into
the event loop via run_coroutine_threadsafe.

9. Your projects → their JD (memorize this mapping)

JD bullet Your proof

Backend services with Both projects: FastAPI with routers, Pydantic models,
Django/Flask/FastAPI global exception handling

Integrate AI/ML models, MeetingMind batch pipeline (upload → 201 →


real-time or batch background transcription + 5-agent extraction →
WebSocket progress); take-home real-time semantic
search

RESTful APIs & Full CRUD + auth APIs; modular router-per-domain


microservices structure

Data pipelines, Audio → faster-whisper → segments → LLM agents →


preprocessing, model PostgreSQL; INT8 quantization for edge at WhiterApps
deployment
Databases: PostgreSQL raw SQL (MeetingMind), MySQL
PostgreSQL/MySQL (take-home) — parameterized, pooled, transactional

Docker / CI/CD Dockerized, 93 tests in CI, HF Spaces single-container


deploy

Frontend (React) React/Vite SPAs in both projects

30-second pitches

MeetingMind: "Local-first meeting intelligence — upload audio, FastAPI stores it and returns
201 immediately, a background worker transcribes with faster-whisper and runs a 5-agent
AgentScope pipeline (Groq with Gemini fallback) extracting summary, decisions, action
items and open questions into PostgreSQL via raw SQL, with live progress over
WebSockets. 93 tests, Dockerized, deployed on Hugging Face Spaces."

Take-home (their assignment): "An AI-powered task and knowledge management system:
FastAPI + MySQL with JWT auth and role-based access enforced through dependency
factories, and semantic search built on sentence-transformers embeddings in a FAISS index
— fully local per the no-external-API constraint, with metadata hydrated from MySQL."

Defend-your-take-home drill (highest-probability deep dive)

Prepare a confident answer for each:

1.​ Why FAISS and not pgvector or Elasticsearch?


2.​ Why all-MiniLM-L6-v2 specifically? (384-dim, strong quality/size tradeoff, CPU-fast)
3.​ Where exactly is RBAC enforced, and what happens on violation? (dependency
factory → 403)
4.​ What happens when the FAISS index and MySQL go out of sync? (rebuild-from-DB
strategy / write-through ordering)
5.​ What breaks at 1M documents? (Flat index → IVF/HNSW; ingestion → background
jobs; pagination on results)
6.​ How would you add tests to it? (TestClient + dependency overrides for auth, fixture
DB)
7.​ What would you change with another week? (refresh tokens, rate limiting,
Alembic-style migrations, Celery for ingestion)

10. Day-before checklist


●​ [ ] Re-read your take-home code top to bottom — every line is fair game.
●​ [ ] Say both 30-second pitches out loud, twice.
●​ [ ] Drill: 401 vs 403, PUT vs PATCH, idempotency, async vs sync in FastAPI.
●​ [ ] Be able to sketch the batch-processing flow (upload → 201 → worker →
status/WS) on paper in 60 seconds.
●​ [ ] Be able to hand-write the JWT login + get_current_user + require_role
trio.
●​ [ ] One sentence ready for "why raw SQL?" and "why FAISS?".
●​ [ ] Have questions for them: "How is the AI team structured — do interns own
features end to end?" / "What does the model-deployment workflow look like today?"

Good luck — you've already built everything this JD describes. The interview is just narrating
it well.

You might also like