How is Linear So Fast?

An interactive technical breakdown of the architecture decisions behind one of the fastest web apps ever built.

0 / 6 sections
🔄
The Core Inversion
Server as sync target, not source of truth

Most web apps have a simple loop: user acts → request sent → server responds → UI updates. Every action waits on the network. Linear broke this entirely.

"The actual database the UI reads from is in the browser, in IndexedDB. Mutations apply locally first, then asynchronously push to the server."
👤
User Action
e.g., close an issue
User performs action in the UI.
📡
Network Request
POST /api/issues/:id — ~50-300ms
UI freezes or shows spinner while waiting for server.
🖥️
Server Responds
Validates, persists, returns updated data
Server is the source of truth. UI can't update until it agrees.
🖼️
UI Updates
Finally reflects the change
Total lag = network round-trip + server processing time.
👤
User Action
e.g., close an issue
🗄️
Local MobX State Updates
Instant — in-memory, no I/O
The UI reads from MobX observables, not the server. Update is instant.
🖼️
UI Updates Instantly
Feels native — zero wait
Component re-renders happen synchronously against local state.
☁️
Server Sync (Background)
Mutation queued and sent async
Network request goes out in the background. User is never blocked by it.
Latency Race — Local vs Network
Local read
LAN (~1ms)
Good WiFi
Mobile 4G
Server RTT
Q: When you update an issue in Linear, when does the UI change?
Optimistic Updates
Assume success, revert on failure

Traditional UIs block until the server responds. Optimistic UIs assume the server will agree and apply changes instantly. If it doesn't, they revert.

Spinners shown
0 for mutations
Perceived latency
~0ms
Failure rate
Very low → revert cost acceptable
Interactive — Optimistic Update Simulation
ENG-101 — Fix sidebar overflow bug
ENG-102 — Add webhook support
ENG-103 — Update dependencies
Q: What happens if the server rejects an optimistic mutation?
🔬
Granular Reactivity with MobX
Only re-render what actually changed

React re-renders by default propagate up then down the component tree. MobX observables allow surgical updates: only the components that subscribed to the exact changed property re-render.

When a single issue's status changes, only the status badge component re-renders — not the parent list, not the sidebar, not the header.
Interactive — Which components re-render?
Choose a field to update on ENG-101:
Component Tree
App
Sidebar
IssueList
IssueRow (ENG-101)
StatusBadge
TitleLabel
PriorityIcon
AssigneeAvatar
Q: What makes MobX particularly suited for apps like Linear?
📦
Bundle Strategy & Load Optimization
50% less code, parallel fetches, service worker cache

Linear migrated through four bundlers — Parcel → Rollup → Vite → Rolldown — optimizing at each step. The key wins:

  • Modern-browser targeting — no polyfills for features all modern browsers support
  • Aggressive code splitting — routes load only what they need
  • Tree shaking — dead code removed at build time
Bundle Size Comparison (relative)
Baseline
loading…
Modern target
loading…
+ Code split
loading…
Final (compressed)
loading…

Without preloading, the browser discovers JS chunks sequentially — a waterfall that delays interactivity.

Linear's HTML contains <link rel="modulepreload"> for every critical chunk. The browser fetches all of them in parallel, before the entry script even runs.
  • Waterfall collapsed into parallel fetches
  • Chunks arrive before they're needed
  • Time-to-interactive drops significantly

A service worker pre-caches ~1,200 assets in the background after the first visit.

Assets cached
~1,200
Subsequent loads
Instant (disk)
Offline support
Partial ✓

On subsequent visits, JS and CSS come from cache — no network round-trip needed at all.

Traditional apps block rendering until the session is verified. Linear does the opposite:

  1. Check localStorage for cached workspace data
  2. If found, render immediately
  3. Verify auth token asynchronously in the sync engine
  4. If auth fails, redirect — but this is rare
The optimistic assumption is: if you have cached data, you're probably authenticated. Don't penalize 99% of loads for the 1% edge case.
Q: What did Linear's use of modulepreload primarily solve?
⌨️
Runtime Responsiveness
Keyboard-first design and animation discipline

Every common action in Linear has a keyboard shortcut. The command palette (⌘K) searches the local data store — no network request required.

Issue creation
C
Command palette
⌘K
Status change
S then 1-5
Search
/ or ⌘K

Since ⌘K results come from the in-memory store, they appear in under 16ms — faster than a single animation frame.

Linear restricts animations to properties that don't trigger layout recalculation:

transform
transform
translate/scale
✓ GPU only
opacity
opacity
✓ GPU only
background
background-color
(occasional)
~ Paint only
layout
width / height
margin / padding
✗ Triggers layout

Click each box to preview. Transition durations are kept to 100-250ms — below the perceptibility threshold.

Q: Why does Linear avoid animating width or height?
🧩
The Whole Picture
Hundreds of decisions, working together

"There's no single thing that makes an app performant. It's the culmination of hundreds of decisions made correctly."
Frontend
React + MobX + TypeScript
Rich text
ProseMirror
Backend
Node.js + PostgreSQL + Redis
Infra
GCP + Kubernetes
Rendering
CSR only (no SSR)
Bundler
Rolldown

How each layer eliminates latency:

  • Local-first data → no loading states for reads
  • Optimistic mutations → no waiting for writes
  • MobX observables → no cascading re-renders
  • Module preloading → no sequential JS waterfalls
  • Service worker → no network hits on repeat visits
  • Keyboard shortcuts → minimal interaction distance
  • Animation discipline → no layout jank
Q: Linear chose client-side rendering (CSR) over server-side rendering (SSR). What's the key reason this works well for them?

All sections complete

You now understand the architecture behind one of the fastest web apps ever built. The key insight: speed is a system property, not a feature.

Learning Reference · How's Linear so fast? A technical breakdown — performance.dev