Design Cursor (AI Code Editor with Agent Edits)
Asked In
Key Challenges
- ·Context chips (@file, selection, open tabs) that budget the prompt window without pasting whole repos
- ·Agent timelines that propose multi-file edits as reviewable events — not silent buffer writes
- ·Pending diffs with Accept / Reject so the model never owns the editor buffer
- ·Streaming patches that can be stopped mid-flight without corrupting committed code
- ·Keeping API keys and tool orchestration behind a BFF while the IDE stays snappy in the browser
2-Minute Answer — High-Level TL;DR
Read this before your interview
HLD Interview Focus
I would design Cursor as three systems sharing one editor buffer. First, context chips — @file, the current selection, open tabs — are first-class UI state that budgets what goes into the prompt, not a giant paste. Second, the composer talks to a BFF that owns keys, tools, and model routing; the browser never calls the LLM API directly. Third, agent output arrives as SSE events: tokens for chat, and patch_proposed for edits. Patches land in a pending-diff queue. Accept merges into the buffer with an undo entry; Reject discards. The sticky rule: proposed edits are a review queue — the model never silently owns the file.
My Approach: Building the Solution
How to think about this problem
Start from the user moment: they highlight a buggy auth helper, @-mention auth.ts, and ask the agent to refactor. Juniors draw a chat panel and a Monaco editor. Seniors draw context chips, an agent timeline, and a pending-diff queue that gates every write into the buffer.
Why This Approach?
Most weak answers treat this as 'ChatGPT + CodeMirror.' That collapses when the interviewer asks what happens if the model rewrites the wrong file mid-stream, or how Stop interacts with a half-applied patch. Once you separate committed buffer from pending patches, Accept/Reject, multi-file tabs, and Abort become natural — the same way regenerate forced a message tree in ChatGPT.
Think of agent edits like a pull request, not autocomplete. Autocomplete inserts as you type. A PR opens a reviewable diff; merge is an explicit human action. Cursor's Accept button is merge. Streaming tokens into the file without review is force-pushing to main.
Requirements: What Makes a Great Solution?
Clarify scope before designing anything
Requirements Exploration Questions
Ask your interviewer these questions to refine requirements
What product modes are in scope?
- ·Ask mode (answer questions, no file writes) vs Agent mode (propose multi-file patches)
- ·Inline edit (Cmd-K style) vs chat sidebar agent
- ·Single-file vs workspace-wide edits
- ·Local / on-device models vs cloud API behind a BFF
What context can the user attach?
- ·@file, @folder, current selection, open tabs, git diff, docs links
- ·Hard token budget — which chips get truncated first?
- ·Privacy: can context leave the machine for cloud models?
What are the latency and safety expectations?
- ·Time-to-first-token for chat; time-to-first-patch for agent edits
- ·Stop must cancel server work and freeze pending patches
- ·Accept/Reject required, or auto-apply with undo?
- ·Conflict if the user typed into the buffer while a patch was pending
Functional Requirements
Must Have — MVP
- ✓Composer with context chips that attach/detach files and selections
- ✓Streaming assistant replies (SSE) with Stop via AbortController
- ✓Agent mode that proposes unified diffs as pending patches
- ✓Accept / Reject per patch (or per hunk) before mutating the buffer
- ✓Multi-file tabs reflecting which files have pending edits
- ✓Agent step timeline (planning, tool call, patch proposed)
- ✓Model / mode picker that gates capabilities (e.g. Agent disabled on tiny models)
Advanced (If Time Permits)
- +Inline ghost-text speculative completions reconciled against the real stream
- +Multi-tab sync of draft composer + pending patches
- +Checkpoint / restore of buffer before a batch of agent edits
- +MCP / tool cards embedded in the agent timeline
Non-Functional Requirements
Performance
- ·TTFT <800ms p50 for Ask; first patch_proposed <2s p50 for small refactors
- ·Editor remains responsive while streaming — never block the main thread on huge diffs
- ·Virtualize long agent timelines and large unified diffs
Reliability
- ·Stop aborts fetch + freezes pending patches mid-stream
- ·If the user edits a file with a pending patch, mark conflict and require re-base or Reject
- ·Network drop keeps partial chat; pending patches stay reviewable
Security & privacy
- ·No provider API keys in the browser — BFF / AI gateway only
- ·Sanitize rendered Markdown / code from the model (XSS)
- ·Respect workspace ignore rules for context gathering
Accessibility
- ·Keyboard Accept / Reject / Stop
- ·aria-live for agent status without shouting every token
- ·Diff view usable with screen readers (line-level announcements on focus)
Architecture (Conceptual)
Component structure, data flow, rendering strategy
Architecture wireframe
browser → model
Cloud
Direct → LLM API
Anthropic, OpenAI — don't
BFF / AI gateway
key stays server-side
LLM API
Anthropic, OpenAI
Self-hosted
vLLM, TGI
Edge
edge inference
Cloudflare, Vercel
Closer to users; great for small models and filters
On-device
WebLLM / ONNX
runs in the browser
Privacy wins; model download + device limits apply
| Approach | Latency | Data privacy | Cost | Good for |
|---|---|---|---|---|
| Cloud API, direct | High | Data leaves the client | Per token | Nothing — it exposes your API key |
| Cloud API behind a BFF | High, plus one hop | Your server controls the flow | Per token + server | Most products |
| Self-hosted model | Medium | Full control | Infrastructure | Regulated / sensitive data |
| Edge inference | Medium | Depends on provider | Per request / plan | Content moderation, light models |
| On-device (WebLLM) | Low after load | Stays on device | User CPU/GPU | Offline, privacy-first demos |
Hover or click a row / column to highlight the matching approach.
Agent edit pipeline
propose → review → apply
Interview note
Pending diff queue
patch_proposed lands here. Accept merges into the buffer; Reject discards. The model never owns the file.
Cursor pending-diff playground
Context chips
export function login(user: string, pass: string) {
return fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ user, pass }),
});
}
@@ auth.ts
-export function login(user: string, pass: string) {
- return fetch('/api/login', {
- method: 'POST',
- body: JSON.stringify({ user, pass }),
- });
-}
+export async function login(user: string, pass: string) {
+ const res = await fetch('/api/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ user, pass }),
+ });
+ if (!res.ok) throw new Error('login failed');
+ return res.json();
+}
Remember: proposed edits are a review queue — never setValue from the raw SSE stream.
Component Hierarchy
Top-Level: CursorIdeShell
- ·Owns FileTab[] + EditorBuffer map (committed text)
- ·Owns ContextChip[] and ComposerDraft
- ·Coordinates AgentStreamController (AbortController + SSE decode)
- ·Owns PendingPatch[] review queue and Accept/Reject mutations
React-Specific Architecture Patterns
Committed vs pending buffers
- ·EditorPane reads only committed text
- ·PendingPatch holds unified diff + target path + baseHash
- ·Accept applies patch → updates buffer + clears pending; Reject drops pending
- ·Trade-off: auto-apply is faster but needs a strong undo stack and conflict UX
Context as budgeted product state
- ·Chips are ordered; overflow truncates lowest-priority chips first
- ·Selection chip always wins over whole-file when both present
- ·Composer sends chip ids / hashes, not necessarily full file bodies every time (BFF can hydrate)
BFF owns tools and secrets
- ·Browser never holds provider keys
- ·BFF enforces rate limits, workspace ACLs, and tool allowlists
- ·Client only decodes typed SSE events into store actions
Why React Query + Zustand?
URL may hold workspace id + active file path. React state / Zustand holds chips, pending patches, and stream status. Committed file text may live in an editor model + IndexedDB draft — but pending patches must stay outside the editor model until Accept.
Data Model (API + Entities + Cache)
Entities, interfaces, cache shape, consistency rules
Separate committed files from reviewable proposals. ContextChip is prompt budget. PendingPatch is a PR-sized unit of work. AgentStep is the timeline row.
ContextChip
PendingPatch
AgentStep
FileTab
ComposerDraft
Interface Design (React Integration)
API contracts, hooks, integration patterns
The headline call is POST /api/agent/run — one SSE stream of typed events. The client never talks to the model provider. Context is sent as chip references; the BFF hydrates file bodies server-side when allowed.
Optimization (Performance + Scale)
Rendering, media, network, and memory optimizations
User asks the agent to refactor auth. Tokens start streaming in chat. Then a patch_proposed event arrives for auth.ts.
The naive move is editor.setValue(newText) as the model streams. It feels alive — until Stop leaves a half-written function, or the model targets the wrong file and production login breaks with no review step.
Where it breaks: there is no Accept gate, no baseHash check, and Abort only hides the spinner while the buffer is already corrupted.
The fix: keep EditorPane on committed text. Park patches in DiffReviewQueue. Accept applies with an undo checkpoint; Reject drops. Batch chat token paints on rAF; virtualize huge diffs. Prefer time-to-first-patch as the agent metric users feel, alongside TTFT for Ask mode.
Pending-diff hot path
- ·Do not mutate Monaco models on patch_proposed — only on Accept
- ·Store unified diffs; apply with a tested patch library + baseHash guard
- ·Virtualize diffs over ~500 lines
- ·Trade-off: auto-apply needs undo stack + conflict UX or you will ship silent overwrites
Streaming UI
- ·Batch token setState on requestAnimationFrame
- ·Agent timeline append is append-only; don't rebuild the whole list each event
- ·Cancel in-flight hydrate when chips change rapidly
Context budget
- ·Estimate tokens per chip; show budget meter
- ·Drop lowest priority chips first when over budget
- ·Prefer selection snippets over whole files when both present
Optimistic Updates
Scene: user runs Agent on a selection in auth.ts.
Naive move: stream the replacement code straight into the editor.
Where it breaks: Stop mid-function; wrong-file overwrite; no review for teammates watching the buffer.
The fix: patch_proposed → PendingPatchCard. Accept applies; Reject discards. Chat can still stream tokens optimistically because chat is not the source of truth for disk.
When not to auto-apply: auth, payments, migrations, anything without a clear undo story.
Remember
Proposed edits are a review queue; the committed buffer is user-owned.
Say this in the interview
“I would stream agent patches into a pending-diff queue and only mutate the editor buffer on Accept, with a baseHash check and undo checkpoint — never setValue from the raw SSE stream.”
Bonus: Implementation Cookbook (Optional)
LLD snippets — only when asked by interviewer
Stretch topics if time remains: speculative ghost-text, MCP tool cards in the timeline, privacy modes (on-device WebLLM), and eval metrics (TTFT vs time-to-first-patch).
Included (Minimal)
- ·Ask vs Agent mode picker UX
- ·Inline vs sidebar diff review
- ·Local model path (WebLLM) for privacy-sensitive workspaces
- ·Checkpoint entire workspace before a multi-file agent batch
Key Takeaways
- ✓Cursor is not ChatGPT beside an editor — context chips, agent steps, and pending diffs share one buffer.
- ✓Never stream patches straight into the file — park them in a PendingPatch queue until Accept.
- ✓Context (@file / selection / tabs) is product state that budgets the prompt, not a paste affordance.
- ✓Talk to the model through a BFF / AI gateway — keys, tools, and rate limits stay server-side.
- ✓Stop must AbortController the agent stream and freeze in-flight pending patches.
- ✓Ask mode answers; Agent mode proposes edits — different UX contracts for the same composer.