Tonsky argues that if a display refreshes 60 times per second and the app fails to deliver a frame, the user sees something objectively wrong — equivalent to an off-by-one or a wrong pixel color. He frames the industry's tolerance for jank as a moral failure of engineering rather than a hardware limitation, walking through the per-vsync millisecond budgets (16.67ms at 60Hz, 8.33ms at 120Hz, 4.17ms at 240Hz) to show the contract is concrete and measurable.
Developers shipping on macOS/iOS with Core Animation or on game engines with strict frame budgets broadly endorsed Tonsky's framing because their toolchains already enforce per-frame deadlines. From their vantage point, treating a dropped frame as a correctness violation is just standard practice, not a radical reframing.
Web developers pushed back by pointing to React's reconciliation overhead, the DOM's layout cost, and the fact that the JavaScript runtime can pause for an unbounded garbage collection at any moment. Their argument is that no amount of engineering discipline can deliver a hard real-time guarantee on a platform whose runtime explicitly does not provide one.
The editorial extracts a practitioner takeaway buried in Tonsky's post: a UI that averages 60fps but drops one frame every two seconds feels worse than one holding a steady 45fps, because humans perceive discontinuities, not averages. Optimizing for mean or median frame time actively misleads engineering teams away from the metric that actually correlates with user-perceived quality.
Niki Tonsky's *Every Frame Perfect* landed on Hacker News with 661 points and the usual flood of disagreement from people who think 60fps is a number you tune toward instead of a contract you keep. The post's central move is rhetorical but precise: a dropped frame is not a performance regression to be triaged later — it is a correctness bug, in the same category as an off-by-one or a wrong pixel color. If the display refreshes 60 times per second and your app fails to deliver a frame for one of those refreshes, the user sees something that is, by definition, wrong.
Tonsky walks through the math: at 60Hz you have 16.67ms between vsyncs. At 120Hz, 8.33ms. At 240Hz, 4.17ms. That budget is not yours alone — the OS compositor, the GPU driver, the display controller, and whatever input pipeline routed the user's gesture all bill against it. By the time your application code is invoked, on most stacks, you have single-digit milliseconds to compute layout, paint, and hand off a finished frame buffer. The interesting part of the post is not the math (any graphics engineer knows this); it is the insistence that the entire industry's tolerance for jank is a moral failure of engineering, not a hardware limitation.
The comment thread split predictably. Native-app developers — particularly those who have shipped on macOS/iOS with Core Animation or on game engines with strict frame budgets — generally agreed. Web developers pushed back, citing React, the DOM, garbage collection, and the genuine impossibility of a perfect-frame contract on a platform whose runtime can pause for an unbounded GC at any time.
The practitioner question buried inside Tonsky's post is: what metric should you actually optimize? The industry default — average FPS or even median frame time — is actively misleading. A UI that hits 60fps on average but drops one frame every two seconds feels noticeably worse than one that holds a steady 45fps, because the human visual system is exquisitely sensitive to discontinuity, not absolute smoothness. The metric that matters is P99 (or P99.9) frame time. Chrome's DevTools surface this. Android's Systrace and macOS's Instruments surface this. Most web monitoring tools do not.
This matters more now than it did five years ago because of two converging trends. First, high-refresh-rate displays have escaped the gaming niche — 120Hz ProMotion on iPhone, 120Hz on most flagship Android devices, 120Hz+ on the majority of new laptops. The frame budget has halved without the underlying software stacks getting twice as fast. Second, the modern web app — React, Vue, Svelte, even the lighter frameworks — has a reconciliation/diff/commit cycle that runs on the main thread and competes directly with the browser's compositor for CPU. The framework you chose for developer ergonomics is, on most pages, the single largest predictor of whether you will hit your frame budget.
The pushback from web developers has merit. The browser is a uniquely hostile environment for frame-perfect work: a single long task on the main thread blocks paint, and the platform offers limited primitives for off-main-thread rendering (Workers can compute but not paint; OffscreenCanvas helps but is not a general solution). React's concurrent mode and the `useTransition` / `useDeferredValue` APIs are explicit acknowledgments that the framework's default scheduling model cannot meet a frame budget, and that the only fix is to give developers tools to mark work as interruptible.
But the native-app side has a sharper point: the iOS and Android platforms have demonstrated, for over a decade, that consumer-grade hardware can hold a perfect-frame contract when the runtime is designed for it. UIKit's CADisplayLink and Android's Choreographer expose the vsync directly. Game engines have run frame-perfect on far weaker hardware than what's in your pocket. The reason your CRUD app drops frames is not that it is impossible to avoid; it is that the abstractions you chose did not make it a first-class concern.
Three concrete actions if you ship UI for a living.
Measure P99 frame time, not average FPS. If your monitoring shows a flat 60fps line, your monitoring is lying to you. Use the browser's Long Tasks API, React Profiler's commit duration histogram, or Android's `FrameMetrics` to capture tail latency. The user's complaint about your app feeling "laggy" is almost always a P99 problem hiding behind a healthy-looking average.
Move expensive work off the critical path. This is not new advice, but Tonsky's framing — that the work either fits in the frame budget or it is a bug — makes the trade-offs concrete. Heavy computation belongs in a Worker. Layout-thrashing reads-after-writes belong in a `requestAnimationFrame` callback. Anything that touches the DOM during a scroll handler is, by Tonsky's definition, a bug waiting to be filed. The CSS `content-visibility: auto` and `contain` properties exist specifically to let the browser skip layout work for offscreen content; if you are not using them on long lists, you are paying frame budget for pixels nobody can see.
Pick frameworks with scheduling primitives, not just rendering primitives. React's concurrent rendering, SolidJS's fine-grained reactivity, and Svelte's compile-time optimizations all attack the same problem from different angles: how do you keep work small enough to fit inside a frame? If your framework's answer is "diff the whole tree on every state change and hope," you have accepted that you will drop frames on any non-trivial UI.
Tonsky's post will be argued about for another week and then absorbed into the standard discourse, much like his earlier work on text input latency. The cultural shift he's pushing — treating dropped frames as bugs in the issue tracker, not entries in a performance backlog — is the same shift the industry made for memory leaks (once tolerable, now embarrassing) and for crash rates (once a quarterly review item, now a release blocker). The frameworks and tools will catch up. The teams that adopt the discipline first will ship visibly smoother products, and their users will notice without being able to articulate why.
Top 10 dev stories every morning at 8am UTC. AI-curated. Retro terminal HTML email.