LearnThatStack Ace your next interview

AI System Design

Hosted LLM APIs as of September 2026 Checked Sep 2026 1 code snippets

The model

  • The model is a remote dependency you didn't train. It's paid per token, rate-limited, sometimes slow or down, and non-deterministic. The design is everything you build around it.
  • The API is stateless. Your server stores each conversation and resends all of it every turn, so every turn costs more than the one before.
  • Latency is two numbers. Time-to-first-token is what a streaming user feels. Total latency adds every output token, and a JSON reply waits for all of it.
  • You pay for tokens, not requests, and output costs several times input. The usual waste is resent context or a big model doing easy work.
  • Failures are normal traffic. Every call needs a timeout and a fallback that fails differently. Evals tell you when quality slips.
Clientrenders the streamApp serverstores the historyLLM gatewaykeys, limits, logsProvidersstateless modelsREQUESTmessagefull history + docscall + API keySTREAMtokenstokens + usageSSE eventsON ERRORfail over to backup
The provider remembers nothing, so your app resends the full history every turn. The gateway adds the key, fails over on errors, and streams tokens back.

Layers & the gateway

the parts every production LLM app has

LLM gateway

Say what an LLM gateway doesEvery app calls it, not the provider. It owns keys, limits, failover and token counts.
List what stays in the appPrompts, retrieval, business logic and chat state.

TrapEvery request goes through it, so it can take everything down. Keep it thin, with health checks and a bypass.

Hosted API or your own GPUs

Pick hosted or self-hostedHosted APIs. Self-host only when a named constraint forces it.
Name the reasons to self-hostData that can't leave your network, steady volume, or weights you must pin or fine-tune.
Keep a self-hosted GPU busyContinuous batching. New requests join at every decode step.
Find a GPU's real capacity limitKV cache memory, not the weights. Long contexts shrink the batch.

TipUtilization decides it. A reserved GPU bills all day, so spiky traffic loses even at high volume.

Latency & streaming

the first token, the last token, and the stream

Two latency numbers

Define time-to-first-tokenThe wait for the first token. Trim the prompt or cache its prefix to cut it.
Define total latencyThe wait for the last token. A shorter answer cuts it.
Say what limits prefill and decodePrefill is bound by compute. Decode is bound by memory bandwidth.

TrapOne blended number hides which half is slow. Track both at p95, per route.

Stream the reply

Say why streaming mattersThe first words show in under a second. Total time barely changes.
Report a failure mid-streamSend an in-band error event. The 200 already went out.
Log and cache a streamed answerKeep a copy on the server. Token counts arrive last.

TrapProxies, gzip and gateways buffer by default. Every hop must pass chunks straight through.

Set a latency budget

Split a 3-second p95 budget100 ms for auth and input checks, 400 for retrieval, 1,200 to first token, 1,300 spare.
Enforce the budgetCarry one deadline with the request. Stages drop optional work when time runs short.
Budget a voice assistantUnder 800 ms, last word to first sound. Endpointing and first token take most of it.

TrapStage p95s don't add up to the end-to-end p95, because slow stages rarely hit the same request. Leave slack.

Context, state & memory

the API forgets, so you store, resend and trim

The API is stateless

Give the model memory of past turnsStore every turn yourself, and resend the whole history each call.
Say why long chats get expensiveTurn 10 pays again for turns 1 to 9. Input grows with the square of the turn count.
Keep prompt caching working in a chatAppend only. Never rewrite earlier turns.

Fit the context window

Define the context windowThe most tokens one request can hold, prompt and reply together.
Budget the windowCount tokens first. Cap chunks at top-k, trim history, and leave room for the reply.
Decide what to dropThe oldest turns or the lowest-ranked chunks. Pick the rule up front.
Say why not to fill itFacts buried in the middle get missed. Every token also adds cost and latency.

TrapGoing over the window is a hard error, not a slower answer.

Store conversation state

Keep the source of truthAn append-only message log in Postgres, keyed by conversation and sequence.
Serve the next turn fastRedis holds recent turns plus a rolling summary, rebuilt from the log on a miss.
Trim history without losing cache hitsLet the window grow past a budget, then fold old turns into the summary in one step.

TrapNever make Redis the source of truth. One eviction loses history.

Prompt & semantic caches

reuse work the model already did

Prompt caching

Say what a prompt cache storesThe KV cache for a prompt prefix, so a repeat skips recomputing it.
Order the prompt for hitsTools, system rules, shared documents, and the live turn last.
Know when it pays offOn repeats. Reads are cheap, but a write can cost more than input.
Measure the hit rateCached read tokens over all input tokens, per route.

TipOn Anthropic, the cache lasts 5 minutes and each read resets it. A prefix under the model's minimum, 512 to 4,096 tokens, never caches.

TrapOne changed character early, like a timestamp, misses the cache for everything after it.

Exact-match and semantic caches

Cache identical promptsHash the prompt. It's never wrong, but hits are rare. Check it first.
Cache prompts that mean the same thingReuse the closest past answer above a similarity threshold.
Key a semantic cacheBy tenant, permissions, model, prompt version and embedding model, not text alone.
Tune the thresholdSweep it on labeled data. Keep false hits under budget.

Trap"Cancel my order" and "can I cancel my order" embed close together. One is an action, one asks about policy.

Routing & cost

the cheapest model that clears the bar

Route or cascade

Pick the model before the callThat's routing. Rules, request length or a small classifier decide.
Try the cheap model firstA cascade. Escalate to a large model only when its answer fails a check.
Know when a cascade saves moneyOnly while few requests escalate. Each one pays for both calls.

TrapThe check must be cheap. Grading with a big model defeats the point.

Design the routing layer

Choose a model for a requestRule out models missing a hard need, like tools. Take the cheapest that meets the tier.
Store the policyAs versioned config. Log the route and version on every request.
Fit routes to eval dataGrade each model per request type. Each type gets the cheapest one that passes.

TipPin a conversation to one model. Switching mid-thread changes its voice and wastes the prefix cache.

TrapA cheap tier that escalates 40% of requests can cost more than the strong model alone.

What an LLM system costs

Price one callInput tokens plus output tokens, each at its own rate.
Pick the number to trackCost per business outcome, like a resolved ticket. Not cost per call.
Estimate cost per conversationSum every turn by token class. Prefix caching cuts the resent history, but only within its TTL.
Report the estimateAs p50 and p90 from real traffic. Rare long chats dominate spend.

TipOutput tokens cost several times input, and a frontier model can cost ten times a small one.

Tenants, limits & queues

share scarce capacity fairly under load

Limit each tenant

Meter tenant loadIn tokens per minute. One request can be 200 tokens or 200,000.
Stop noisy neighborsA reserved floor per tenant, plus a shared pool for bursts.
Count output you can't know yetReserve input plus estimated output at admission. Refund the difference afterward.
Bill teams for their usageThe gateway tags every call with a cost center and refuses untagged calls.

TrapCap concurrency per tenant too. 500 slow requests starve everyone, even inside a token budget.

Handle 429s and backpressure

Stay under provider limitsA local token bucket that reserves estimated input plus output tokens.
Retry a 429Honor retry-after, then back off with full jitter. Keep a retry budget.
Push back when your queue fillsKeep the queue bounded. Fail fast with your own 429 and Retry-After.

TrapAn unbounded queue only turns 429s into client timeouts.

Scale across providers and regions

Plan capacity per regionQuotas are fixed per key or region. Size each region for its load after a failure.
Balance the loadBy remaining token headroom, not round robin.
Keep prefix cache hitsHash the stable prefix to one provider. Spill over only when it's out of headroom.
Stop a failover retry billing twiceKey results by the client's request id in a globally replicated store.

TrapDon't autoscale the app tier on CPU. CPU stays flat while calls pile up, so scale on in-flight requests.

Queue async and long jobs

Accept a 500-page document202 AcceptedReturn a job id. The client polls, or you call a webhook.
Resume a failed jobSave each chunk's result as it lands. A retry resumes at chunk 340, not 1.
Drain tenants fairlyDeficit round robin per tenant, counted in estimated output tokens.
Skip work nobody waits forStamp each job with a deadline, and drop expired ones at dequeue.

TipIf results can wait hours, use the Batch API. It costs about half as much.

TrapRequire an Idempotency-Key on submit. Otherwise a client retry starts a second job.

Failures & fallbacks

stay up when the model isn't

Match the fix to the failure

Retry once, then fail overHandle a timeout or 5xx
Back off, or use a smaller modelHandle a 429 rate limit
Refuse plainly and log itHandle a content filter block
One repair try with the errorHandle output that breaks the schema
Raise the token capHandle a reply cut off at the token cap
the right movewon't helpa mistake

TipA fallback must fail differently. A retry repeats the same path, so it only fixes brief blips.

TrapSending a blocked prompt to a looser model elsewhere is filter shopping. Auditors read it that way too.

SLOs and a degradation ladder

Pick the SLOsAvailability, p95 time-to-first-token and a sampled quality score.
Count a request as availableOnly for a valid answer that isn't a refusal. HTTP 200 isn't enough.
Degrade in rungsDrop the reranker, then the big model, the live call, the instant reply, and last the answer.
Choose the rungFrom the error budget's burn rate, not by hand.

TipTag every degraded reply, or quality metrics look healthy while users get less.

Reliable JSON and tool calls

Enforce the output shapeOne JSON Schema per tool, sent in strict mode and reused in your validator.
Check what strict mode can'tMeaning. Check business rules, like whether that order id exists.
Stop a retry running a tool twiceDerive an idempotency key from the tool call id.

TrapStrict modes support only part of JSON Schema. Keep schemas flat.

Guardrails & grounding

stop bad input, bad output and bad actions

Where guardrails go

Check inputBefore the call. It saves tokens but can't see what the model will say.
Check outputAfter the call. It catches the real harm, but it fights streaming.
Check tool callsBefore they run, with a policy per tool. It stops the side effect itself.

TipHold back a short tail of streamed text and release what's behind it. A check can then stop a reply in time.

Ground answers or abstain

Abstain before generatingWhen the top chunks score below a threshold.
Make claims checkableReturn each claim with its chunk ids. Reject any citing text you never retrieved.
Verify each claimScore it against its cited text with an NLI model. Cut what isn't supported.
Cite web sources in an answer engineAnswer only from numbered chunks, tagging each sentence with its chunk ids.

TrapScore coverage apart from accuracy. Otherwise guessing looks better than an honest abstention.

Defend against prompt injection

Handle retrieved textKeep it in a data slot, never pasted into the system prompt.
Authorize a tool callCheck the user's permissions in code, not the model's plan.
Break the lethal trifectaNever give one agent private data, untrusted content and a way to send data out.
Sandbox an agent's toolsA microVM or container per run, with network egress denied by default.

TrapNo filter reliably stops injection. A passing classifier score is never the reason an action runs.

Keep PII away from providers

Decide what may leaveClassify the data first. The highest classes never reach a hosted API.
Mask PII in what you sendSwap sensitive spans for placeholders like PERSON_1. Restore them in the reply.
Cover what the detector missesBack the filter with zero-retention, no-training terms. Detector recall is never 100%.

TrapTreat embeddings as PII. If they leave the region, so did the data.

Human review and escalation

Send a request to a humanOn low confidence, a guardrail hit, a risky tool call, or when the user asks.
Pause an action for approvalSave it with an idempotency key and a timeout. Deny on timeout if it's irreversible.

TipStore each decision as a label with a reason code. It feeds evals, so escalations fall.

Evals, tracing & rollout

see when quality drops, and ship changes safely

What to measure

Split evals from monitoringOffline evals gate each change. Online monitoring watches live traffic.
List the signals to watchSchema failures, groundedness, refusals, regenerates, escalations, p95 latency and cost.
Keep an LLM judge honestPin its version, and check it against human labels.

TipTag each request with its prompt and model version. Slice by tenant and intent too.

Roll out a prompt or model change

Shadow the change firstMirror sampled traffic and drop the replies. Tools dry-run and writes are blocked.
Canary the changeStart near 1% and ramp, bucketed by user id. Roll back automatically on bad metrics.
Move off a retiring modelPin snapshot ids, then replay evals and recorded traffic through the new one.

TrapOutput is non-deterministic, so exact diffs prove nothing. Compare paired outputs with a judge model.

Improve from production data

Log a trace you can replayInclude the retrieved chunk ids, prompt version and model version.
Stop overfitting your evalsKeep a frozen holdout set you never tune on.

TrapThumbs and clicks are biased, because unhappy users stay silent. Label a small random sample too.

Designs interviewers ask for

the classic prompts, one card each

Support bot over company docs

Answer from the docsOnly from retrieved passages, with a source link per claim.
Escalate to a humanWhen nothing relevant comes back, the user is angry or asks twice, or it's billing or refunds.
Hand off without a cold startPass the transcript, the passages and why it escalated.

TrapDeflection rate alone rewards a bot that stonewalls. Track it against CSAT.

Enterprise RAG with access control

Store permissionsPut the allowed group and user ids on every chunk as ACL metadata.
Apply permissions at query timeAs a filter during the search, never after it.
Handle an index that lagsRe-check permissions on cited chunks, and tombstone deletes.

TrapA post-filter fetches ten hits, then drops most. Recall collapses for users with narrow access.

AI coding assistant

Serve autocompleteA small fill-in-the-middle model, 200 to 500 ms at p95.
Keep autocomplete cheapCancel the in-flight request on every keystroke.
Serve agent modeA large tool-calling model. It proposes diffs and runs commands in a sandbox.

TipMeasure autocomplete by acceptance rate, and whether a suggestion is still there 30 seconds later.

Found this useful? Pass it on.
Pro · $10/mo

This sheet stays free. With Pro, you can:

  • Every answer on this sheet. Read the full answer to all 54 questions it links, not just the 7 free ones.
  • My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
  • PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
  • The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.