Skip to content
Dev Tools Article

How Linear Gets to Sub-10ms UI Updates: The Architecture Behind the Speed

Linear's legendary snappiness isn't magic — it's a deliberate set of architectural decisions. Here's how they work, and which patterns you can steal today.

AI
DevClubHouse Curation
Jun 8, 2026 · 5 min read · 0 comments

A traditional CRUD app updating an issue takes roughly 300ms. Linear does the same thing in a few milliseconds. According to Dennis Brotzky's deep-dive at performance.dev, there's no single silver bullet — it's a foundation built right from day one, then compounded by hundreds of small decisions.

The Browser IS the Database

The most critical architectural choice Linear made is inverting the classic request loop. Instead of click → HTTP request → server query → repaint, Linear keeps the actual working database in the browser via IndexedDB. Mutations apply locally first, then asynchronously push to the server, which broadcasts deltas back to other clients over WebSocket.

The practical result: there's nothing to wait for. The UI re-renders synchronously off an in-memory update (a MobX observable), and network I/O is invisible to the user.

Contrast the two patterns directly:

// Traditional CRUD
async function updateIssue({ issue }) {
  showSpinner();
  const response = await fetch(`/api/issues/${issue.id}`, {
    method: "PATCH",
    body: JSON.stringify({ title: issue.title }),
  });
  const updated = await response.json();
  setIssue(updated);
  hideSpinner();
}

// Linear's model
issue.title = "Faster app launch";
issue.save();

The first line updates an in-memory MobX observable. The second queues a transaction the sync engine batches and flushes to the server in the background. No spinner. No wait.

This wasn't an afterthought. Co-founder Tuomas said at a 2024 conference: "Literally the first lines of code that I wrote was the sync engine, which is very uncommon to what you usually do when you're a startup." They accepted the complexity of local-first architecture before writing a single feature.

You Don't Need a Custom Sync Engine

Building a full sync engine is a serious undertaking most teams shouldn't attempt. But the underlying principle — UI responsiveness must not depend on network latency — is achievable today with libraries you probably already use.

Optimistic updates with SWR get surprisingly close:

// Optimistic mutation with SWR
mutate(
  `/api/issues/${issue.id}`,
  { ...issue, title: "Faster app launch" },
  false // skip revalidation until background fetch resolves
);

The pattern is the same: update state immediately, validate in the background, roll back only on failure. TanStack Query has equivalent support via useMutation's onMutate / onError hooks. Most web apps feel slow because the UI waits for each network round trip to complete before touching state — even though the request succeeds the overwhelming majority of the time.

High-leverage wins from optimistic UI:

  • Eliminate unnecessary loading spinners
  • Update state immediately on user intent
  • Validate server-side asynchronously
  • Roll back only on actual errors (rare in practice)

A Deliberately Boring Stack

Linear's frontend stack is notable for what it doesn't include:

Layer Choice
UI runtime React + react-dom
State / reactivity MobX (observable graph, granular re-renders)
Bundler Rolldown-Vite + plugin-react-oxc (mid-2025)
Local store idb wrapping IndexedDB
Rich text ProseMirror + y-prosemirror (Yjs CRDT)
Styling Emotion runtime + StyleX compiled to atomic CSS
Worker RPC Comlink

No edge database, no React Server Components, no exotic meta-framework. The backend is equally vanilla: Node.js + TypeScript, PostgreSQL on Cloud SQL (issues table partitioned 300 ways), Redis on Memorystore as an event bus and sync cursor store, and Kubernetes on GCP with Cloudflare Workers as a multi-region edge proxy.

The standout call is sticking with client-side rendering. CSR gets criticized for slow initial loads, but Linear demonstrates that with the right local-first architecture, CSR can feel more instant than SSR alternatives — because the browser already has the data.

MobX deserves special mention. Its observable graph enables granular re-renders: only the components subscribed to a changed observable re-render, not a subtree. That's the runtime complement to local-first data — you're not just avoiding network waits, you're also avoiding unnecessary DOM reconciliation.

The Takeaway

Linear's performance isn't the product of any single trick. It comes from a consistent philosophy applied at every layer: hide the network from the user. The more loading states you can eliminate, the more native the app feels.

You can start without rewriting your architecture. Audit your mutations and flip them to optimistic. Replace spinners with immediate state updates backed by background validation. The network will keep doing its job — users just won't have to wait for it.

Discussion 0

Join the discussion

Sign in with GitHub to comment and vote.

Sign in with GitHub

No comments yet

Be the first to weigh in.

Related Reading