Design AI lab systems with clear goals, scale math, APIs, data models, overload behavior, permissions, eval gates, and operational debugging paths.
A system-design round asks you to turn ambiguous model-product requirements into a reliable backend. A strong answer is rarely the most complex architecture. It names the product goal, sizes the hard constraint, then adds queues, caches, model routing, eval gates, permissions, or human review only where requirements force them.
Use codebase, model-serving, RAG, support-automation, and deployment examples when you need a concrete neutral domain: code search, incident summarization, policy-gated billing credits, eval dashboards, and internal agent workflows have real latency, privacy, and debugging constraints.
Open with:
I will keep the first design simple, size the constraints early, then add queues, caches, sharding, model routing, or eval gates only when the requirement forces them.
Then follow this order:
| Prompt | Core design pressure | Common miss |
|---|---|---|
| Scalable web crawler | frontier, politeness, dedupe, retry policy | ignoring per-host backpressure |
| Model API gateway | keys, workspaces, rate limits, request IDs, model routing | no support/debug path |
| Inference scheduler | queueing, batching, latency, fairness, overload | optimizing throughput before latency SLO |
| Long-running coding agents | durable tasks, tools, checkpoints, permissions | unclear recovery and cancellation |
| Permission-aware retrieval | ACLs, freshness, deletion, tenant isolation | retrieving first and filtering later |
| Evaluation/safety monitor | offline evals, incidents, red teams, launch gates | treating eval as a dashboard only |
Most frontier AI/backend prompts are combinations of these patterns. Identify the dominant pressure before drawing boxes.
| Pattern | Prompt signal | Architecture moves | Follow-up pressure |
|---|---|---|---|
| Model gateway | multi-provider, teams, API keys, quotas | auth, entitlements, route policy, quota buckets, request log | streaming, fallback semantics, budget caps, support replay |
| Inference scheduler | latency, batching, GPUs, overload | admission queue, batcher, worker pool, KV/cache accounting, fairness | tail latency, starvation, preemption, 429 vs 503 policy |
| Permission-aware retrieval | enterprise docs, ACLs, deletion, citations | source connectors, ACL snapshots, filtered retrieval, audit trail | revocation SLO, fail closed, hybrid search, stale index |
| Agent execution platform | long-running tasks, tools, repo access | task state machine, sandbox, tool policy, event log, artifacts | cancellation, retries, secret handling, human review |
| Eval and rollout gate | quality launch, regressions, red team | offline evals, golden sets, canary gates, rollback triggers | slice failures, noisy judges, metric ownership |
| Data ingestion platform | connectors, freshness, normalization | ingestion jobs, versioned records, dead-letter queue, backfill | schema drift, reprocessing, dedupe, deletion |
| Observability and support | request IDs, incidents, "why did this happen?" | traces, decision records, support views, replayable metadata | privacy-safe debugging, retention, sampling |
| Abuse and safety control | policy, misuse, irreversible actions | policy engine, rate limits, review queues, kill switches | false positives, bypass attempts, emergency disable |
Use this table to avoid architecture soup. A model gateway prompt doesn't need a vector database unless the product asks for retrieval. A retrieval prompt doesn't need autonomous agent planning unless the user asks the system to take actions.
Interviewers often stress the first design with a new constraint. Answer by naming the boundary you'll change.
| Follow-up | Good move | Bad move |
|---|---|---|
| "Traffic spikes 10x" | admission control, queue SLO, tier fairness, explicit 429 or 503 cause | unlimited queues |
| "Permissions change quickly" | ACL freshness SLO, tombstones, fail-closed sensitive sources | retrieve first, filter after generation |
| "Provider is down" | policy-approved fallback, circuit breaker, surfaced degraded mode | silently change model behavior |
| "Users need cancellation" | durable cancel flag, cooperative checks, sandbox termination | best-effort UI button only |
| "Support asks why" | request ID, policy version, route decision, retrieved IDs, trace | raw logs with no decision record |
| "Eval passes but users complain" | slice analysis, online canary metrics, incident cases into regression suite | argue offline eval is enough |
| "Costs doubled" | token accounting, cache hit tracking, model route policy, budget alerts | vague autoscaling |
Before the last five minutes, your design should contain:
If any item is missing, add it before adding another component.
Use this matrix to avoid over-practicing one design shape. A strong prep set covers control planes, data planes, eval loops, and human operations.
| Prompt family | Clarify first | Core architecture | Stress follow-ups |
|---|---|---|---|
| Multi-model gateway | streaming, spend policy, beta access | auth -> quota -> route policy -> provider adapter -> audit | provider outage, 10x spike, fallback correctness |
| Inference scheduler | latency SLO, model size, GPU pool | admission queue -> batcher -> workers -> KV accounting | fairness, preemption, hot tenant, overload |
| Enterprise retrieval | revocation SLO, source types, citations | ingestion -> ACL snapshots -> filtered retrieval -> answer trace | deletion, stale ACLs, hybrid search, audit |
| Coding-agent service | tool permissions, write access, job length | task API -> sandbox -> event log -> artifacts -> review | cancellation, retries, secrets, stuck tools |
| Eval platform | launch criteria, judges, slices | dataset registry -> runners -> scorer -> report -> gate | noisy labels, drift, red-team cases, ownership |
| Data ingestion platform | freshness, schema drift, backfills | connectors -> normalized records -> version store -> index | dedupe, reprocessing, tombstones, source outage |
| Safety policy layer | allowed actions, review threshold | request context -> policy engine -> tool gate -> audit | false positives, emergency block, bypass attempts |
| Observability platform | support questions, retention, privacy | trace IDs -> logs/metrics/events -> support view -> replay | PII, sampling, high cardinality, incident workflow |
| Realtime voice or chat | latency budget, turn-taking, fallback | session gateway -> streaming model -> tool loop -> handoff | interruption, partial output, moderation, cost |
| Experiment system | unit of assignment, ramp plan, metrics | assignment service -> config -> logging -> analysis -> rollback | interference, sample ratio mismatch, guardrails |
Every design should include one small calculation. It doesn't need perfect precision; it needs to expose the bottleneck.
| System | Minimum math |
|---|---|
| Gateway | requests/minute, tokens/minute, worst-case output cap |
| Retrieval | documents, chunks/document, embedding storage, update rate |
| Scheduler | arrival rate, average service time, queue wait, GPU memory |
| Agent service | concurrent jobs, sandbox time, log/artifact storage, retry budget |
| Eval platform | examples per suite, runs per release, judge/model cost |
| Voice/chat | p95 latency budget split across network, model, tools, synthesis |
| Ingestion | source QPS, backfill duration, dedupe key cardinality |
Use this phrasing:
I will size the constraint that most affects the design. If that assumption changes, the architecture boundary I would revisit is
X.
When a design has many possible components, keep a visible decision log:
| Decision | Chosen | Rejected | Why | Reversal signal |
|---|---|---|---|---|
| Queue placement | before provider call | inside every adapter | one overload policy | adapter-specific SLO needed |
| Retrieval filter | before rerank/generation | post-generation filter | privacy fails closed | none for sensitive docs |
| Fallback model | policy-gated | automatic on any error | behavior may change | explicit customer opt-in |
| Agent writes | human-reviewed | direct writes | irreversible action risk | narrow, reversible tool scope |
This keeps the conversation inspectable. Interviewers can disagree with a choice and still see that the choice was deliberate.
Ask questions that change the design. Avoid long discovery interviews. Pick the three questions most likely to affect API, state, scale, or safety.
| Prompt family | High-signal clarifying questions |
|---|---|
| Gateway | Are responses streamed? Which limits are hard stops? Which route changes require user opt-in? |
| Scheduler | What latency SLO matters: time to first token, full completion, or queue wait? Are tenants isolated by tier? |
| Retrieval | What revocation SLO is required? Should unknown ACL freshness fail closed? Are citations mandatory? |
| Agent platform | Which actions are irreversible? Can jobs push branches, or only produce patches? What must cancellation guarantee? |
| Eval platform | Which metric blocks launch? Who owns bad labels? How are incident cases promoted into regression suites? |
| Ingestion | What freshness target matters? Are deletions hard deletes, tombstones, or retention-window deletes? |
| Voice/chat | What is the p95 latency target? Is barge-in required? What happens when a tool call is slow? |
| Observability | Who asks "why did this happen?" Support, safety, billing, or engineering? What data must be redacted? |
Use this opener:
I will ask three questions because they change the design:
latency or scale,permission or safety, anddebug or rollout.
Practice with a visible clock. A strong design round leaves time for follow-ups instead of spending 30 minutes drawing boxes.
| Time | Output |
|---|---|
| 0-4 min | goal, users, success metric, top risks |
| 4-9 min | functional and non-functional requirements |
| 9-14 min | one scale calculation that exposes the bottleneck |
| 14-20 min | API and durable data model |
| 20-28 min | request path with the smallest architecture that works |
| 28-35 min | reliability, overload, permissions, and support/debug flow |
| 35-40 min | rollout, eval gate, and rollback path |
| 40-45 min | tradeoffs, reversal signals, and interviewer follow-ups |
If the interviewer interrupts early, jump to the dominant constraint:
The part that most changes the design is
constraint. I will size that first, then show the request path it forces.
After each mock design, score the answer on evidence, not on whether the diagram looked impressive.
| Signal | 0 | 1 | 2 |
|---|---|---|---|
| Goal | user and success metric unclear | goal named | goal tied to product risk or operator need |
| Requirements | list is generic | key functions named | tradeoffs and non-goals named |
| Scale math | no calculation | one rough estimate | estimate changes an architecture choice |
| API | hand-wavy endpoints | basic contract | contract includes errors, IDs, and auth context |
| Data model | boxes only | core entities | retention, versioning, and isolation considered |
| Request path | too broad | happy path clear | failure and support path included |
| Overload | queues forever | generic rejection | admission policy, fairness, degraded mode, and correct 429 vs 503 ownership |
| Permissions | mentioned late | auth boundary named | fail-closed behavior and audit path included |
| Observability | dashboard language | metrics listed | request ID, traces, decision records, and owner actions |
| Rollout | "canary" only | staged launch | eval gates, rollback trigger, and incident feedback loop |
| Tradeoff | one obvious choice | rejected option named | downside, mitigation, and reversal signal named |
Target 18+ before treating a prompt family as ready. A lower score means repeat the same prompt with a different product surface.
Treat each prompt like a 45-minute design round. First write requirements, scale math, API, data model, request path, failure modes, and rollout plan. Then open the solution guide.
Design an API gateway for teams calling multiple LLM providers through one company platform.
Prompt details:
Clarifying questions to ask:
Strong answer shape:
POST /v1/responses, GET /v1/requests/{id}, admin endpoints for keys and limits.429 with Retry-After when this tenant, key, or client exceeds its limit. Return 503 with a retry hint when healthy callers can't be admitted because fleet or provider capacity is exhausted. Route to an approved fallback only when product policy allows the behavior change.Common misses: no support path, no versioned policy record, no overload rejection, and no separation between authentication and authorization.
If asked about streaming, reserve quota from an estimate before admission, cap output length, then reconcile actual tokens at the end of the stream. If asked about fairness during a 10x spike, split queues by organization or tier so one incident can't starve everyone else. If asked about beta models, answer with a versioned entitlement check and a kill switch:
The gateway should log
policy_version,entitlement_id,route_id, andquota_bucket_idon every request so support can explain both accepted and rejected traffic.
Design retrieval for an internal assistant that answers employee questions from company documents.
Prompt details:
Clarifying questions to ask:
Strong answer shape:
Common misses: filtering after generation, no deletion story, no citation IDs, and no way to explain eligibility.
If asked about deletion, describe a fast tombstone path first, then slower compaction of embeddings and chunks. If asked about stale permissions, define a freshness SLO per source and fail closed for sensitive sources when the ACL snapshot is too old.
For auditability, store enough to replay the eligibility decision: user identity, groups, source ACL version, query filters, retrieved chunks, citations shown, and model response ID. That lets an admin answer "why this document?" without exposing unrelated private documents.
Design a service that accepts repository tasks and runs a coding agent asynchronously.
Prompt details:
Clarifying questions to ask:
Strong answer shape:
Common misses: no cancellation semantics, no sandbox boundary, no artifact model, and no distinction between retrying a read and replaying a write.
If asked about retries, separate infrastructure retries from agent-action retries. Retrying a sandbox provision is safe; replaying a commit, comment, or external API write needs idempotency or human review.
If asked about cancellation, make it cooperative and durable: set a cancellation flag, stop scheduling new tool calls, terminate the sandbox after a grace window, persist partial logs, and mark artifacts as incomplete. If asked about secrets, say the sandbox receives scoped, short-lived credentials and the event log redacts values before storage.
The gateway is the front door. It authenticates API keys, resolves workspace and organization limits, estimates request cost, routes to a model or queue, and emits a request ID that support can follow.
Design checklist:
429; fleet or provider overload returns 503. Both carry a request ID and a bounded retry hint instead of silent queue growth.Use Python to sanity-check rate math before drawing capacity boxes:
1requests_per_minute = 4_000
2avg_input_tokens = 1_200
3avg_output_tokens = 450
4tokens_per_minute = requests_per_minute * (avg_input_tokens + avg_output_tokens)
5tokens_per_second = tokens_per_minute / 60
6
7print("tokens_per_minute:", tokens_per_minute)
8print("tokens_per_second:", round(tokens_per_second))1tokens_per_minute: 6600000
2tokens_per_second: 110000The estimate is an admission-time reservation, not the final bill. A streaming response needs a maximum output budget, then a reconciliation step when the stream ends:
1remaining_before = 10_000
2reserved_tokens = 1_800
3actual_tokens = 1_520
4
5remaining_after_admission = remaining_before - reserved_tokens
6remaining_after_reconcile = remaining_after_admission + (reserved_tokens - actual_tokens)
7
8print("after admission:", remaining_after_admission)
9print("after reconcile:", remaining_after_reconcile)1after admission: 8200
2after reconcile: 8480For LLM serving, the scheduler is where latency, cost, and fairness meet. NVIDIA documents in-flight batching as a way to interleave context and generation work so GPUs are used more efficiently while latency stays under control.[1] Mention these metrics:
Start capacity planning with a measured workload-specific fleet capacity, not a generic GPU estimate:
1measured_capacity_tokens_per_second = 125_000
2expected_demand_tokens_per_second = 110_000
3
4headroom = measured_capacity_tokens_per_second - expected_demand_tokens_per_second
5headroom_percent = headroom / measured_capacity_tokens_per_second * 100
6
7print("headroom_tokens_per_second:", headroom)
8print("headroom_percent:", round(headroom_percent, 1))1headroom_tokens_per_second: 15000
2headroom_percent: 12.0For an interview-sized overload policy, estimate queue wait before admission. This approximation deliberately stays simple. A real scheduler also tracks prefill, decode work, GPU memory, and measured tail latency.
Keep rejection ownership visible. 429 Too Many Requests says the caller exceeded a tenant, key, or client policy and could succeed after its quota window resets. 503 Service Unavailable says the service fleet or an upstream provider lacks capacity for an otherwise eligible request. A full tenant bucket can produce 429 even when GPUs are idle; a saturated fleet can produce 503 for a tenant that is under quota.
1def admit(queued_tokens: int, service_tokens_per_second: int, max_queue_wait_seconds: float) -> bool:
2 estimated_wait = queued_tokens / service_tokens_per_second
3 return estimated_wait <= max_queue_wait_seconds
4
5print(admit(queued_tokens=20_000, service_tokens_per_second=125_000, max_queue_wait_seconds=0.25))
6print(admit(queued_tokens=50_000, service_tokens_per_second=125_000, max_queue_wait_seconds=0.25))1True
2FalseFor enterprise retrieval, the critical rule is: don't retrieve private data and filter it after generation. Permission constraints must be part of candidate selection, ranking, and auditing.
Architecture pieces:
The ACL snapshot and hybrid index both feed candidate selection. That connection matters: unauthorized chunks shouldn't reach the reranker or model context.
For sensitive sources, encode the freshness decision as a fail-closed policy:
1def source_is_eligible(snapshot_age_seconds: int, freshness_slo_seconds: int, sensitive: bool) -> bool:
2 if sensitive and snapshot_age_seconds > freshness_slo_seconds:
3 return False
4 return True
5
6print(source_is_eligible(snapshot_age_seconds=20, freshness_slo_seconds=60, sensitive=True))
7print(source_is_eligible(snapshot_age_seconds=90, freshness_slo_seconds=60, sensitive=True))
8print(source_is_eligible(snapshot_age_seconds=90, freshness_slo_seconds=60, sensitive=False))1True
2False
3TrueFilter tombstones and ACLs before ranking. The tiny fixture below makes the order visible:
1documents = [
2 {"id": "public-access-policy", "groups": {"employees"}, "deleted": False, "score": 0.82},
3 {"id": "finance-plan", "groups": {"finance"}, "deleted": False, "score": 0.99},
4 {"id": "old-handbook", "groups": {"employees"}, "deleted": True, "score": 0.95},
5]
6user_groups = {"employees"}
7
8eligible = [
9 document
10 for document in documents
11 if not document["deleted"] and document["groups"] & user_groups
12]
13ranked_ids = [document["id"] for document in sorted(eligible, key=lambda item: item["score"], reverse=True)]
14
15print(ranked_ids)1['public-access-policy']Long-running agent infrastructure has to persist intent, tool calls, artifacts, checkpoints, logs, and permissions. The main design risk isn't just failed execution. It's uncontrolled execution.
Cover:
Write legal transitions down before discussing workers. That prevents a cancellation or retry from jumping into an impossible state:
1ALLOWED_TRANSITIONS = {
2 "queued": {"provisioning", "canceled"},
3 "provisioning": {"running", "failed", "canceled"},
4 "running": {"blocked", "review", "failed", "canceled"},
5 "blocked": {"running", "review", "failed", "canceled"},
6 "review": {"running", "complete", "canceled"},
7 "failed": set(),
8 "canceled": set(),
9 "complete": set(),
10}
11
12def can_transition(current: str, target: str) -> bool:
13 return target in ALLOWED_TRANSITIONS[current]
14
15print(can_transition("running", "review"))
16print(can_transition("complete", "running"))1True
2FalseRetries need a policy boundary too. Reads and sandbox provisioning can retry automatically. External writes need an idempotency key or review:
1def retry_mode(action: str, has_idempotency_key: bool = False) -> str:
2 if action in {"repo_read", "sandbox_provision"}:
3 return "automatic"
4 if has_idempotency_key:
5 return "automatic-with-idempotency"
6 return "human-review"
7
8print(retry_mode("repo_read"))
9print(retry_mode("create_commit"))
10print(retry_mode("post_comment", has_idempotency_key=True))1automatic
2human-review
3automatic-with-idempotencyAn evaluation monitor isn't just a dashboard. It turns fixed expectations and incident discoveries into a regression suite, then blocks launch when a critical slice fails.
1checks = {
2 "retrieval_recall": (0.94, 0.92),
3 "citation_faithfulness": (0.93, 0.95),
4}
5permission_leaks = 0
6
7failures = [
8 name
9 for name, (observed, minimum) in checks.items()
10 if observed < minimum
11]
12if permission_leaks > 0:
13 failures.append("permission_leaks")
14
15print("launch_allowed:", not failures)
16print("failures:", failures)1launch_allowed: False
2failures: ['citation_faithfulness']Keep online canaries reversible. Offline evals can pass while latency, provider errors, or permission failures regress under real traffic:
1def rollout_decision(error_rate: float, p95_latency_ms: int, permission_failures: int) -> str:
2 if permission_failures > 0:
3 return "rollback"
4 if error_rate > 0.01 or p95_latency_ms > 900:
5 return "hold"
6 return "expand"
7
8print(rollout_decision(error_rate=0.004, p95_latency_ms=720, permission_failures=0))
9print(rollout_decision(error_rate=0.004, p95_latency_ms=720, permission_failures=1))1expand
2rollback429 behavior, and fleet 503 behavior.Answer every question, then check your score. Score 75% or higher to mark this lesson complete.
10 questions remaining.
Questions and insights from fellow learners.