Tonsky: Every Dropped Frame Is a Bug, Not a Trade-Off

5 min read 1 source clear_take
├── "Smoothness is an absence, not an average — one dropped frame destroys the illusion regardless of average FPS"
│  ├── Nikita Prokopov (Tonsky) (tonsky.me) → read

Tonsky argues that at 120Hz developers have only 8.33ms per frame, and missing that budget even once produces a visible stutter because the human eye is a differential sensor tuned to detect discontinuities. He contends the industry's reliance on average FPS and p50/p95 latency dashboards systematically hides the exact failures users actually perceive.

│  └── @ravenical (Hacker News, 378 pts) → view

Submitted the post to HN where it climbed to 378 points, reflecting strong resonance with the argument that aggregate metrics mask perceptible jank. The submission's traction suggests broad agreement among native-leaning developers that frame consistency matters more than average throughput.

├── "The frame budget halved at 120Hz but JavaScript-heavy frameworks didn't adapt, making React's runtime tax untenable on modern displays"
│  └── Nikita Prokopov (Tonsky) (tonsky.me) → read

Tonsky frames the current crisis as a math problem: ProMotion and high-refresh Android cut the frame budget in half while frontend frameworks grew heavier, not lighter. He implies that React's bet on developer ergonomics over runtime cost was defensible at 60Hz on M-series Macs but starts failing on mid-range Android at 120Hz.

├── "Web developers face constraints that make frame-perfect rendering impractical, and native-dev critiques miss those realities"
│  └── @Hacker News web-dev commenters (Hacker News) → view

Per the editorial synthesis, the 139-comment thread split predictably with web developers pushing back that the browser's rendering pipeline, GC pauses, and cross-platform constraints make 8.33ms budgets unrealistic. They argue Tonsky's Skija/native perspective doesn't translate to the layered abstractions the web platform forces on them.

└── "Declarative-but-fast UI is now achievable — SwiftUI, Jetpack Compose, and Signals prove the React tradeoff is no longer necessary"
  └── Nikita Prokopov (Tonsky) (tonsky.me) → read

Tonsky positions the post against a backdrop where SwiftUI and Jetpack Compose have given native devs declarative ergonomics without the runtime tax, and Signals are eating React's lunch in the discourse. His implicit argument is that the industry now has working proof that you don't have to choose between developer experience and frame-perfect rendering.

What happened

Nikita Prokopov — better known as Tonsky, the Clojure-and-Skija maintainer who has been quietly waging a one-man war on UI jank for a decade — published 'Every Frame Perfect' on his blog. It climbed to 378 points on Hacker News in a single afternoon, with the comment thread splitting along the predictable axis: native devs nodding, web devs explaining why he doesn't understand their constraints.

The thesis is simple enough to fit on a sticky note. At 120Hz, you have 8.33 milliseconds to produce a frame. If you miss that budget — by one millisecond, once — the user sees it. Not statistically. Not as a regression in some aggregated FPS counter. They see the specific frame that didn't arrive, because the eye is a differential sensor and a stutter against an otherwise smooth scroll is exactly the kind of contrast it evolved to notice.

Tonsky's argument is that the industry has been measuring the wrong thing. We track average frame rate, p50 latency, sometimes p95. We ship dashboards that say '59.2 FPS, looking good!' and then ship products that feel terrible. The mismatch is because smoothness isn't an average — it's an absence. One bad frame in a hundred ruins the illusion of motion in a way that no amount of good frames can repair.

Why it matters

The post lands at a specific moment in the frontend conversation. React Server Components are shipping. Signals are eating React's lunch in the discourse. SwiftUI and Jetpack Compose have made declarative-but-fast UI a thing native devs can actually have. And every one of these frameworks is, at some level, a bet about where the frame budget should go.

React's bet — well-litigated by now — is that developer ergonomics are worth a runtime tax, and the tax can be paid because computers are fast. That bet is fine at 60Hz on a M-series Mac. It starts losing money at 120Hz on a mid-range Android. The frame budget halved when ProMotion shipped, but the JavaScript got heavier, and nobody recut their architecture to match.

The HN comments revealed the fault line cleanly. One thread, started by a former Google Maps engineer, argued that frame-perfection is a religion for people who don't ship features. Another, from a games industry refugee, pointed out that game engines have shipped at locked frame rates for thirty years and the only reason web devs treat it as exotic is because they've never had a deadline a CPU could miss. Both are right, which is what makes the debate productive.

Tonsky's specific contribution is to name the economic mechanism. Frame drops are externalized costs: the user pays them in eye strain and irritation, the team pays nothing, so the team optimizes for the metrics they can see. If your dashboard shows mean FPS and your competitor's shows dropped-frame count, you'll converge on different products. He's not arguing for a new framework. He's arguing for a new instrument panel.

The practical evidence backs him. Discord rewrote its scroll list in 2022 and the headline win wasn't faster scrolling — it was the elimination of a class of micro-stutter that nobody had measured but everybody had felt. Linear's product cred comes partly from the fact that the interactions feel deterministic; you press a key, the thing happens, every time, in the same number of milliseconds. That's not magic. That's someone counting dropped frames.

What this means for your stack

If you're shipping anything with scroll, drag, or animation as a core interaction, the actionable read is this: replace your FPS chart with a dropped-frame histogram and watch your priorities rearrange themselves overnight. The Chrome DevTools Performance panel has had this data for years; almost nobody pulls it. `requestAnimationFrame` callbacks that overrun get logged. You can ship a five-line script that pushes that count to your analytics provider this afternoon.

For React specifically, the move is less about adopting Compiler or signals and more about budget discipline. Profile the worst frame in a 60-second scroll, not the mean. If a single reconciliation pass eats 14ms, the problem isn't the renderer — the problem is that you're doing 14ms of work in a place where you have 8. Either move the work (web workers, deferred state, transitions) or admit that the interaction will stutter and design around it. The third option, which most teams pick by default, is to ship the stutter and not talk about it.

For native, the message is closer to vindication. SwiftUI's diffing is fast enough that frame-perfection is achievable without heroics; Compose got there in version 1.5; Flutter's been there since its impeller renderer landed. The stacks that bet on retained-mode-plus-fast-diff are now collecting the dividend. The web's 'document with scripts' lineage is the unpaid debt — every framework is a workaround for the fact that the browser was never built to hit a frame deadline.

There's also a hiring signal in here. Engineers who can articulate why frame-perfection matters, and who reach for the right tools (jank profiling, GPU timing, compositor analysis), are doing a different job than engineers who can wire up a component library. The skill is closer to systems programming than to product engineering, and the labor market hasn't fully priced it yet. If you're a senior frontend dev with that skill, name it on your resume.

Looking ahead

The pessimistic read is that none of this changes. Most products are bought on feature lists and screenshots; nobody puts 'p99 frame time' on the marketing page; the install base of 60Hz screens is enormous and forgiving. The optimistic read is that the device base is shifting underneath us — ProMotion is standard on iPhones, 120Hz is standard on flagship Androids, and the gap between 'good enough' and 'noticeably bad' is shrinking with every refresh cycle. Tonsky's post will land in 2026 the way 'designing for retina' landed in 2013: obvious in retrospect, fought over at the time, and eventually a baseline expectation. The teams that internalize it early get a quality moat that no AI codegen pipeline can replicate, because frame budgets aren't a code problem — they're a discipline problem. And discipline still has to be done by humans.

Hacker News 839 pts 274 comments

Every Frame Perfect

→ read on Hacker News

// share this

// get daily digest

Top 10 dev stories every morning at 8am UTC. AI-curated. Retro terminal HTML email.