# Build a Code Editor: CodeMirror, Monaco, and the Tradeoffs

Source: https://www.techinterview.org/post/3233475355/build-code-editor-codemirror-monaco-tradeoffs/
Updated: 2026-05-06 · techinterview.org

"Build a code editor" is the meta-version of the rich-text-editor question — the same content-editable challenges plus syntax-aware features (highlighting, autocomplete, lint, multi-cursor). Web-based code editors live inside CodeSandbox, Replit, Cursor, GitHub, and many SaaS apps. The interview tests whether you understand the tradeoffs between the major open-source editors and the integration work involved.

## Clarify scope

- Read-only or full editing?

- Single-language or multi-language?

- Autocomplete from local symbols or LSP?

- Diff view, blame, inline annotations?

- Mobile support? (most code editors are desktop-first)

## The big two: CodeMirror 6 vs Monaco

### CodeMirror 6

- Modular, tree-shakable, modern (rewrite from CM5)

- Smaller bundle (~50KB minimum, grows with features)

- Tree-sitter or Lezer parsers for syntax

- Strong on customization; many extensions

- Used by Sourcegraph, Replit, Codepen

### Monaco

- The VS Code editor extracted

- Larger bundle (~1MB)

- Built-in TypeScript / JS / JSON / CSS / HTML language services

- LSP-style completion, diagnostics, hover

- Used by GitHub Codespaces, CodeSandbox

Pick CodeMirror if you need to ship a small bundle and customize heavily. Pick Monaco if you want VS-Code-grade features out of the box and the bundle size is acceptable.

## Other options

- Ace — older, mature, smaller community in 2026

- Lexical (with code blocks) — for embedded code in a doc, not a full editor

- Custom: only if you have unusual constraints (mobile-first, very small bundle)

## Syntax highlighting

- Tokenization: turn source into typed tokens

- CodeMirror uses Lezer or tree-sitter; Monaco uses Monarch grammars or TextMate grammars

- Theming: token-type → color mapping; Light/Dark/HC themes

- Performance: incremental parsing on edit, not full re-parse

## Autocomplete

Three tiers:

- Local: words from the current buffer (CodeMirror @codemirror/autocomplete)

- Language-aware: typescript-language-features for TS/JS in Monaco; LSP for many in CodeMirror

- AI-powered: stream completion from an LLM (Cursor, Copilot pattern)

## Multi-cursor

Both editors support; the API differs. Cmd+D to add cursor at next match; Cmd+Click to add cursor at click. Implementation:

- Selection state is an array of ranges, not a single range

- Edits apply to all cursors

- Insertions handled per-cursor with offset adjustments

- UI: render multiple visible carets

## LSP integration

Language Server Protocol for full IDE features:

- Run language server (e.g., typescript-language-server) somewhere — server, web worker, or via WebContainer

- JSON-RPC messaging between editor and language server

- Editor displays completions, diagnostics, hover, go-to-definition

- Latency budget: 100–300 ms per request

## Performance considerations

- Tokenization on the main thread for short files; web worker for long ones

- Virtualize the line render: only visible lines mounted

- Avoid heavy React wrappers around per-line components

- Debounce expensive operations (lint, format) on type

## Accessibility

- Both CodeMirror and Monaco have screen-reader modes; verify with NVDA / VoiceOver

- Keyboard navigation full

- High-contrast theme available

- Focus indicator and aria attributes

## Mobile considerations

- Both editors handle mobile reasonably; Monaco is heavier

- On-screen keyboard interactions can be janky; test on real devices

- Touch selection differs from mouse; both editors support it

- For mobile-first products, consider a simplified custom editor

## Diff view

- Both editors support side-by-side diff (Monaco built-in; CodeMirror via @codemirror/merge)

- Inline diff for code review UI

- Three-way merge view for conflict resolution

## Collaboration

- Real-time multi-user editing requires CRDT (Yjs has bindings for both editors)

- Y.Text + ProseMirror or @codemirror/collab

- Cursor and selection broadcast via the same channel

## What separates senior from staff

Senior candidates discuss CodeMirror vs Monaco tradeoffs and integrate one into a React app. Staff candidates discuss LSP integration, multi-cursor mechanics, performance under large files, and the bundle-size strategy. Principal candidates raise the collaborative-editing layer (CRDT) and the language-server hosting story (browser-based via web workers / WebContainer vs server-side).

## Frequently Asked Questions

### Can I use Monaco in mobile web?

Possible but heavy. The bundle and the on-screen keyboard interactions are challenging. CodeMirror is friendlier on mobile.

### How do I add AI autocomplete?

Implement a CompletionSource that calls an LLM API. Stream tokens; insert as ghost text; commit on Tab. The Cursor / Copilot pattern.

### Should I use a language server in the browser?

For TypeScript / JavaScript, yes — Monaco bundles it. For Python or other languages, run via WebContainer (StackBlitz) or server-side. Latency is the main concern.
