FastAPI & REST API Interview Cookbook
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)
Strategy: answer in 2–3 sentences, then offer your project as proof. "...and that's exactly how
I handled uploads in MeetingMind."
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.
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).
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.
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.
● 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)
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."
app = FastAPI()
class TaskCreate(BaseModel):
@[Link]("/tasks", status_code=201)
...
@[Link]("/tasks/{task_id}")
...
Type hints drive parsing: wrong types → automatic 422 with a structured error body. You
write zero validation boilerplate.
class UserOut(BaseModel):
id: int
@[Link]("/users/me", response_model=UserOut)
def get_db():
conn = [Link]()
try:
finally:
[Link](conn)
@[Link]("/tasks")
...
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).
● 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.
# api/[Link]
# [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.
app.add_middleware(
CORSMiddleware,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@[Link]("/tasks/{task_id}")
task = fetch(task_id)
if not task:
return task
@app.exception_handler(Exception)
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."
@[Link]("/meetings", status_code=201)
meeting = create_meeting(...)
background_tasks.add_task(process_meeting, meeting["id"])
@asynccontextmanager
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.
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.
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
@[Link]("/auth/login")
user = get_user_by_email([Link])
token = [Link](
SECRET_KEY, algorithm="HS256",
try:
except JWTError:
user = get_user_by_id(int(payload["sub"]))
if not user:
return user
return user
return checker
@[Link]("/users/{user_id}", status_code=204)
...
● 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.
● 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."
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.
[Link]([Link](vecs, dtype="float32"))
@[Link]("/search")
● 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.
@[Link]("/meetings", status_code=201)
...
UploadFile spools to a temp file (memory-safe for large files), unlike bytes which loads
everything into RAM.
client = TestClient(app)
def test_create_task_requires_auth():
def test_get_task_not_found(auth_headers):
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.
Backend services with Both projects: FastAPI with routers, Pydantic models,
Django/Flask/FastAPI global exception handling
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."
Good luck — you've already built everything this JD describes. The interview is just narrating
it well.