System Design Arena
System Design: Windsurf / Codex
Provide on-device + cloud-assisted code completions, chat explanations, and repo context for millions of developers.
Case Study
AI Pair Programming
Same conversation, diagrams, and wrap-up you expectβnow framed with clearer scaffolding and iconography.
Prompt
Build an AI pair-programming assistant. Requirements: context-aware completions, repo-wide search, chat explanations, offline fallback, telemetry, and privacy guardrails.
Interview snapshot
- β’ Topic: AI Pair Programming
- β’ Expected depth: 45 - 60 minutes
- β’ Focus areas: APIs, scale estimation, resilient architecture
- β’ Wrap-up: risk, monitoring, disaster recovery
Key takeaways
- β’ IDE plugins for VSCode/IntelliJ/etc. Provide completions, inline chat, test generation, doc lookup.
- β’ Latency: simple completion < 200 ms; chat < 1 s.
- β’ POST /api/context/upload - IDE posts context bundle (files, git diff, metadata).
ποΈ Interview mode
Practice this like a real interview
Don't read the answer first. Work the framework against the bare prompt, then compare against what the interviewer expected at each step.
Scorecard
What the interviewer is evaluating
Staff-level signal
Does the candidate design for the 200ms completion budget β context assembly, caching, and speculative inference β rather than treating it like a chatbot?
π§ Staff engineer judgment
Every millisecond of context assembly is stolen from inference. If your retrieval pipeline is clever but slow, the product is worse than a dumb fast one.
Calibration
The common (bad) answer
IDE β API β LLM β completion
β Why this scores poorly: Misses the defining constraint: completions race the userβs next keystroke. 2-second chatbot latencies are product death; the design is really a context+cache+latency system.
β What a strong answer adds
- Context engine: what fits in the prompt (open file, imports, repo map, recent edits) under a token budget.
- Two-tier models: tiny fast model for inline completions, big model for chat/edits.
- Prefix caching β the file prefix barely changes between keystrokes; reuse KV cache.
- Debounce + cancellation: most requests die before inference finishes; make that free.
- Privacy tiers: on-prem/local inference for code that cannot leave the machine.
Build it up
Step-by-Step Walkthrough
An AI pair programmer lives or dies on a ~200ms budget: slower than a keystroke feels broken. The design journey is about what you can afford to compute per keystroke versus per session β context assembly, model tiers, and a ranking loop that learns from every accept and reject.
Naive: ship the current file to a big model
Version one is an IDE plugin that sends the active file to a large LLM on every pause in typing and inlines whatever comes back. It produces impressive demos and three immediate failures: round-trips to a large model take seconds against a ~200ms feel-threshold; the model suggests functions that do not exist because it cannot see the rest of the repo; and at ~2M daily developers triggering ~200 completions each β on the order of 400M requests/day β the GPU bill is ruinous.
Each failure names a subsystem: a latency budget forces model tiers, missing context forces repo indexing, and cost forces caching and ranking. The rest of the design is those three, in order.
Context assembly is the real product
Completion quality is mostly retrieval quality. A repo indexer parses the codebase into an AST and symbol graph and embeds code chunks into a sharded vector index β this runs per commit, off the hot path. Per keystroke, the plugin assembles a cheap local bundle (active file, cursor window, recent edits, open buffers, git diff) and ships it to a context service that merges in retrieved snippets: the callee's signature from another file, the matching test, a similar usage elsewhere.
A prompt builder ranks all of it into the model's context window under a hard token budget β nearest-scope first, retrieval filling the remainder. The split to narrate: indexing is expensive and asynchronous, assembly is cheap and synchronous, and the ~200ms budget is spent almost entirely on assembly plus inference.
?The question interviewers reach for
'Context window is full β what do you drop?' Rank by proximity to the cursor: enclosing function, then siblings and imports, then retrieved cross-file snippets, then docs. A small model with the right 2K tokens beats a large model with the wrong 8K β that sentence reframes the whole design as a retrieval problem.
Tier the models to fit the latency budget
One model cannot serve both a ghost-text suggestion (needs <200ms) and 'explain this module' (can take ~1s). Split the fleet: a small distilled model handles inline completions from warm pools with streaming output, a large model serves chat and multi-file edits, and a compact on-device model provides last-mile suggestions when the network disappears β the resilience answer interviewers ask for by name.
Caching attacks the volume: identical context prefixes recur constantly (every developer in a repo shares its skeleton), so completion caching on context hashes and server-side prefix reuse cut both latency and GPU cost. At ~20K requests/s peak, every cached hit is a GPU-second saved.
Rank completions with the feedback you already get
Users grade every suggestion for free: accepted, rejected, accepted-then-immediately-edited. That telemetry (~400M events/day, ~5K/s) flows into a pipeline feeding a ranking service β generate a handful of candidates, score them with a lightweight learned ranker on context features and historical acceptance, and show the winner. Acceptance rate becomes the platform's north-star metric, and ranker updates ship through offline eval against logged sessions before touching production.
This loop is what separates a product from an API wrapper: the model may be a commodity, but the ranking signal β which suggestions this codebase's developers actually keep β compounds and is yours alone.
The policy engine makes it sellable
Source code is the crown jewels, so a policy engine wraps the whole flow: secret scanning redacts keys and tokens from context bundles before they leave the IDE, tenant isolation keeps one company's embeddings out of another's retrieval, context bundles are stored ephemerally (~24h) with zero-retention contracts for enterprises, and every access lands in an audit log.
Close on the converged shape β plugin β context service β prompt builder β tiered inference, with the indexer feeding retrieval, telemetry feeding ranking, and policy wrapped around everything. Then name the failure mode you designed away: cloud unreachable degrades to the local model behind a circuit breaker. Never silence, just smaller suggestions.
!The redaction trap
Redact secrets client-side, in the plugin, before upload β server-side redaction means the secret already crossed the wire and touched your logs. Interviewers plant this one deliberately; catching it unprompted is worth a level.
Blueprint
Architecture Diagram
The narrated components above, laid out as an actual flow β so you can see how a request moves through the system, not just read a list of pieces.
IDE plugin
IDE plugin
context service
storage (object store
storage (object store + metadata DB)
Context retrieval +
Context retrieval +
Context retrieval + vector search
prompt builder
inference service (LLM)
Local model (on-device)
Local model (on-device) handles fallback suggestions
Policy/safety engine redacts
Policy/safety engine redacts secrets
Policy/safety engine redacts secrets, enforces tenant boundaries
Telemetry pipeline feeds
Telemetry pipeline feeds ranking service
abuse detection
Flow Diagram
How the API maps to this flow
- 1
POST /api/context/uploadβ IDE posts context bundle (files, git diff, metadata). - 2
POST /api/completionsβ request completion with prompt/context handles streaming tokens. - 3
POST /api/chatβ conversational Q&A with code references. - 4
GET /api/search?query=β repo-wide semantic search. - 5
POST /api/feedbackβ sends rating + suggestions for completions.
?But why does this actually hold up at scale?
Assume 2M daily active devs, each triggering 200 completions/day β 400M requests/day (~4.6K/s avg, 20K/s peak).
Interview flow
Dialogue timeline
Interviewer
What are the core components?
Candidate
IDE plugin collects context (files, diffs), sends to backend for completions/chat. Backend uses LLMs + vector search. Need policy engine, caching, telemetry.
Interviewer
How do you manage latency?
Candidate
Short completions must return in <200 ms. Use local caches, distill smaller models for simple suggestions, fallback to cloud for complex queries.
Interviewer
Explain context ingestion.
Candidate
Plugin builds context bundle (active file, project graph, tests), redacts secrets, uploads to context service. Vector index stores embeddings for retrieval.
Scoping
Requirements & trade-offs
Functional Requirements
- βIDE plugins for VSCode/IntelliJ/etc. Provide completions, inline chat, test generation, doc lookup.
- βRepo indexing: parse AST, symbol graph, embedding generation.
- βCloud inference service with multiple model tiers (fast vs smart).
- βOffline mode using smaller local model for last-mile suggestions.
- βTelemetry + feedback loop for ranking improvements.
- βSecurity: secret scanning, privacy controls per enterprise tenant.
Non-Functional Requirements
- βLatency: simple completion < 200 ms; chat < 1 s.
- βScalability: millions of active devs, thousands of requests per second.
- βData privacy: tenant isolation, zero retention options for enterprises.
- βResilience: degrade gracefully if cloud unreachable (local model fallback).
Blueprint
Architecture modules
Module 1
API Endpoints
- β’POST /api/context/upload - IDE posts context bundle (files, git diff, metadata).
- β’POST /api/completions - request completion with prompt/context handles streaming tokens.
- β’POST /api/chat - conversational Q&A with code references.
- β’GET /api/search?query= - repo-wide semantic search.
- β’POST /api/feedback - sends rating + suggestions for completions.
Module 2
Back-of-the-Envelope
- β’Assume 2M daily active devs, each triggering 200 completions/day β 400M requests/day (~4.6K/s avg, 20K/s peak).
- β’Context bundle avg 200 KB β need efficient uploads + dedupe. 2M bundles/day β 400 GB raw; store ephemeral (24h).
- β’Vector index: 100K repos * 1M symbols each = 100B embeddings; need sharded ANN (FAISS) with coarse filtering.
- β’Telemetry: 400M events/day -> 4.6K/s; store aggregated metrics for ranking updates.
Module 3
System Diagram Notes
- β’IDE plugin -> context service -> storage (object store + metadata DB).
- β’Context retrieval + vector search -> prompt builder -> inference service (LLM).
- β’Local model (on-device) handles fallback suggestions.
- β’Policy/safety engine redacts secrets, enforces tenant boundaries.
- β’Telemetry pipeline feeds ranking service + abuse detection.
Module 4
Design Playbook
- β’Clarify IDE integration, languages supported, offline requirements.
- β’Explain context ingestion, indexing, retrieval flow.
- β’Discuss inference tiers (fast vs smart), streaming completions, caching.
- β’Cover security: secret redaction, tenant isolation, audit logging.
- β’Highlight feedback loop: telemetry -> ranking -> deployment.
- β’Plan for resilience: circuit breakers, local fallback, feature flags.