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

“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.

Scroll to Top