Back to System Design
System Design
hard
mid

How would you design browser like features such as tabs, history, and shortcuts?

Designing features like tabs, history/back-forward, bookmarks, find-in-page, or a mini-renderer. Model the state machine, choose the right data structures (stacks for history, trees for the DOM), handle persistence and lifecycle, manage memory, and reason about performance and edge cases.

7 min read·~30 min to think through

"Browser-like features" — tabs, navigation history, bookmarks, find-in-page, a tab manager, a mini DOM renderer — test whether you can model a stateful system, pick data structures, and reason about lifecycle, memory, and edge cases. Approach it like any system design: clarify scope, model state, then go deep.

1. Clarify scope

Browsers are huge — pin down which feature and which behaviors. "Tabs" could mean just switching, or also drag-reorder, persistence, lazy-loading, and crash recovery. Agree on the subset.

2. Model the state + data structures

Each feature has a natural structure:

  • Navigation history (back/forward)two stacks (back, forward). Navigate → push current to back, clear forward. Back → pop back, push to forward. Same shape as undo/redo.
  • Tabs — an ordered list of tab objects { id, url, title, state, lastActive } + an activeTabId. Reorder = array move.
  • DOM / mini-renderer — a tree; rendering = recursive traversal.
  • Find-in-page — text index + a list of match ranges + a current-match pointer.
  • Bookmarks — a tree (folders) or flat list with tags.

Naming the right structure quickly is the core signal.

3. Lifecycle & persistence

  • Persistence — tabs/history/bookmarks survive restarts → localStorage/IndexedDB; restore on load.
  • Lifecycle — tabs mount/unmount; suspend inactive tabs (don't keep 100 live) and restore on activation.
  • Crash recovery — periodically snapshot session state.

4. Memory management — the part that separates levels

A browser can't keep everything live:

  • Lazy-load — don't render a tab's content until activated.
  • Suspend / evict — serialize inactive tabs' state, free their DOM/memory, rehydrate on return (this is exactly what real browsers do).
  • Cap in-memory instances; LRU-evict the rest to storage.

5. Performance

  • Virtualize if there can be thousands of items (a huge history list, a tab overflow menu).
  • Debounce expensive work (find-in-page search on input).
  • Keep switching instant — pre-warm the likely-next tab.

6. Edge cases (always raise these)

  • History: navigating mid-stack truncates forward; duplicate entries; max depth.
  • Tabs: closing the active tab (which becomes active next?), closing the last tab, restoring a closed tab.
  • Find: zero matches, wrapping past the last match, matches inside collapsed/hidden content, live DOM changes.
  • Persistence: corrupted/stale stored state; storage quota exceeded.

The framework

Scope it down → model state with the right data structure → handle lifecycle & persistence → manage memory (lazy/suspend/evict) → performance → edge cases. Browser-like features are really "stateful systems with constrained resources" — show you can reason about all of it, not just the happy path.

Follow-up questions

  • How would you model browser back/forward history?
  • How do real browsers keep memory bounded with many open tabs?
  • How do you persist and restore session state across restarts?
  • What edge cases matter for a find-in-page feature?

Common mistakes

  • Not scoping the feature down — trying to design 'a browser'.
  • Picking the wrong data structure (e.g. not using two stacks for history).
  • Keeping every tab fully live in memory.
  • Ignoring persistence, restoration, and crash recovery.
  • Designing only the happy path.

Performance considerations

  • Memory is the constraint — lazy-load, suspend, and LRU-evict inactive instances to storage. Virtualize large lists (history, tabs). Debounce search. Keep tab switching instant via pre-warming.

Edge cases

  • Navigating from the middle of history (forward stack truncation).
  • Closing the active or last tab.
  • Find-in-page with zero matches or live DOM changes.
  • Corrupted or quota-exceeded persisted state.

Real-world examples

  • Browser tab suspension/discarding to bound memory.
  • Back/forward as two stacks; session restore after a crash.

Senior engineer discussion

Seniors treat these as resource-constrained stateful systems: scope down, name the right data structure immediately (two stacks, trees, indexed ranges), then go deep on lifecycle, persistence, and especially memory management (lazy/suspend/evict — what real browsers do). They proactively enumerate edge cases instead of designing only the happy path.

Related questions