Skip to Content

Backend Infrastructure — Interview Q&A

Building an AI application is only one part of the job. Building an AI application that is scalable, fault-tolerant, observable, and production-ready is what differentiates an AI engineer.


Q1 — How do you manage shared resources in FastAPI using lifespan events?

What? FastAPI’s lifespan async-context-manager runs once at startup, yields control to the app, and runs shutdown code on app teardown. It’s where you initialise expensive resources (LLM clients, DB connection pools) and tear them down cleanly.

Why? Without it, every request could leak connections, file descriptors, or LLM clients. With it, your app boots deterministically and shuts down gracefully.

How? Pattern:

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: build resources ONCE
    db_pool = await create_db_pool()
    llm_client = build_groq_client()
    yield {"db_pool": db_pool, "llm_client": llm_client}
    # Shutdown: release things cleanly
    await db_pool.close()
    await llm_client.close()
app = FastAPI(lifespan=lifespan)

Routes access the resources via request.app.state.* or Depends(...).

3 Scenarios

Scenario 1 — Resource leak without lifespan A httpx.AsyncClient is constructed inside every route handler. The connection pool leaks 1 socket per request; OOM 4 hours into the day. Fix: instantiate the client in lifespan, attach to app.state, share across requests.

Scenario 2 — Cold-start latency The first user request takes 8 seconds because pgvector is initialising. Fix: during lifespan startup, send a no-op query to warm the LLM client and the DB pool.

Scenario 3 — Graceful shutdown missing A rolling deploy kills the pod while long requests are still in flight; clients see TCP RST. Fix: in lifespan exit, await in-flight tasks up to 30s, then pool.close().


Q2 — How would you optimise Docker images for faster deployments?

What? A multi-stage build that discards build tools from the running layer, keeps only what’s needed at runtime.

Why? Because 2 GB images take 4x longer to start in cluster-autoscale scenarios; egress costs balloon on every pull.

How? Four levers:

  1. slim base (python:3.12-slim): ~150 MB savings vs full python.
  2. Multi-stage: compile in stage 1, copy only .so and .dist-info to stage 2.
  3. CPU-only PyTorch: --index-url https://download.pytorch.org/whl/cpu saves ~1.5 GB.
  4. --no-cache-dir: avoid keeping wheel caches.

3 Scenarios

Scenario 1 — Image is 2.3 GB Diagnosis: CUDA torch + Spacy model + dev tools. Fix: CPU-only torch (200 MB), Spacy wheel in a layer, slim base. Result: 1.3 GB.

Scenario 2 — Cold-start 30s on Cloud Run Diagnosis: 2.3 GB image must download before app can run. Fix: target <500 MB; Cloud Run cold-starts drop to 6s.

Scenario 3 — Image pull failures during k8s rollouts Diagnosis: timeouts on next-mile Google mirror. Fix: pre-pull using a DaemonSet; or mirror to private registry.


Q3 — How do you reduce network traffic when deploying containers across Kubernetes clusters?

What? Multi-cluster or multi-region Kubernetes adds significant network egress. Optimise image, artefact, and request traffic.

Why? Because inter-region bandwidth is expensive (Cloudfront, GCP inter-region); a 1 GB model-image pulled into 50 nodes per cluster = $20 per pull.

How? Three layers:

  1. Image registry locality: pin per-cluster registries; cache images rather than re-pulling.
  2. Reduce image size (see Q2).
  3. DaemonSets for shared artefacts: sidecar that downloads the large model ONCE per host, mounts into pods.

3 Scenarios

Scenario 1 — Multi-region production Approach: regional image mirrors; DaemonSet sidecar for embedding models. Each pod reads from local volume, no network per-request.

Scenario 2 — Multi-tenant platform Approach: shared model sidecar; pods share via CSI volume. Saves 1.5 GB per pod × 200 pods = 300 GB of pod-local copies.

Scenario 3 — Edge deployment (Cloudflare Workers or Fastly Compute) Approach: ultra-small model (1B params) + remote LLMs for reasoning. Edge-co-local inference; no inter-region traffic.


Q4 — What challenges arise in multi-region Kubernetes deployments?

What? Operating the same workload across multiple cloud regions introduces latency, data-residency, and consistency challenges.

Why? Because regulatory compliance (FCA, GDPR) requires data to stay in jurisdiction; latency is unfixable across regions; consistency requires design.

How? Three patterns:

  1. Active-passive (one region primary, others standby): simple, but slow failover.
  2. Active-active (each region serves traffic): complex, requires careful conflict resolution.
  3. Regional-only data: each region has its own data store; user routed to nearest region.

3 Scenarios

Scenario 1 — FCA data residency Approach: per-region Postgres + per-region Redis. UK users only hit UK region. Other regions are dev/staging.

Scenario 2 — Latency-sensitive (real-time agent) Approach: regional user routing based on geo-DNS. Cross-region fallback only on outage.

Scenario 3 — Synchronous global writes Approach: eventual consistency. Writes are async-replicated. Reads can return stale by ~100 ms.


Q5 — How would you design an application that survives zonal failures?

What? Designing for resilience to single-zone outages within a region. Critical because autoscaler responses to a zone failure can deplete a region.

Why? Because zonal failures happen: AWS, GCP, Azure have zone-down incidents every quarter.

How? Three pillars:

  1. Multi-AZ deployment: 3+ replicas spread across zones.
  2. HPA with minReplicas: don’t autoscale to 0 under load. Triggers cluster thrash.
  3. PodDisruptionBudget: protect against voluntary interruptions (rolling deploys).

3 Scenarios

Scenario 1 — Zonal outage during load spike Effect: one zone’s pods die; HPA spins up 2 zones worth of new pods; cluster-wide outage. Fix: minReplicas: 6 spread across 3 zones; PDB minAvailable: 4.

Scenario 2 — Rolling deploy during high traffic Effect: 2/3 pods die during deploy, capacity drops below request rate. Fix: PDB minAvailable: 2 ensures 2 pods always run during the rollout.

Scenario 3 — Cascading pod evictions from node pressure Effect: nodes are tight on memory, kubelet evicts low-priority pods. Fix: priorityClass on critical pods; PDB enforces minimum.


Q6 — When would you prefer async programming + asyncio over threading?

What? Async I/O in Python uses cooperative multitasking — explicit await boundaries swap control without OS thread switching. Threading uses OS threads, one per concurrent unit.

Why? Because in production I/O-bound code (LLM calls, DB queries, HTTP), async outperforms threads on cost (one network event loop vs N OS threads) and latency (no thread-pool contention).

How? Rule of thumb:

3 Scenarios

Scenario 1 — 500 concurrent LLM calls Pick: async. Each LLM call is 1-3s I/O; threads would saturate the GIL and the GIL switch cost is high.

Scenario 2 — Parallel CPU-bound math (matrix multiplication) Pick: processes. Threads would serialise behind the GIL; processes spawn real parallelism.

Scenario 3 — Mixed: 50 I/O calls then a CPU computation Pick: asyncio + asyncio.to_thread for the CPU step. The I/O is concurrent; CPU is offloaded.

Advertisement