HN DigestHacker Newsletter, digested

Issue #808#Code

The browser's main thread is expensive

TL;DR

Web jank usually comes from code holding the browser's single main thread, not from slow code. Lee Sun-Hyoup describes two families of fixes, each with an interactive demo. One spends the main thread's time better by splitting, batching, prioritizing and deferring work. The other moves work to the compositor or a web worker, or drops it entirely.

The article

  • JavaScript, event handling, style, layout and paint all share the main thread. Only compositing runs elsewhere. A 60Hz frame lasts 16.6ms but leaves about 10ms of usable budget, and half that at 120Hz. Any task over 50ms counts as a long task. INP and TBT both measure, in effect, how long the main thread was blocked.
  • Splitting a big job and yielding between pieces makes the total slower. But it lets input and frames run in the gaps. setTimeout yields hit a 4ms minimum once nested more than five deep, so React's scheduler yields through MessageChannel. scheduler.yield() resumes ahead of other queued tasks. Time-sliced work should anchor to the rAF frame timestamp and take about 5ms per frame.
  • Splitting lowers throughput. When data arrives faster than it renders (backpressure), batching helps. Debounce or throttle events, and redraw at most once per requestAnimationFrame. Group DOM writes so the pipeline's fixed cost is paid once.
  • A hand-built MessageChannel queue lets urgent work jump the line. An example is a user clicking a photo whose preview isn't built yet (the idle-until-urgent pattern). Deferral covers code splitting, and filling feed items only when IntersectionObserver reports them near the viewport. The author passes on content-visibility: auto because of a Safari bug.
  • transform and opacity animate on the compositor and stay smooth while the main thread is blocked. top and left force layout every frame. FLIP turns a real layout change into one layout plus a transform animation. Grouping reads before writes avoids layout thrashing.
  • Web workers suit heavy, DOM-free computation such as the seam-carving demo, as long as the work outweighs postMessage's copy cost. Transferring an ArrayBuffer avoids the copy. The biggest wins come from not doing the work at all: dropping stale log entries, merging updates to keep only the latest value, and memoizing.

Why it matters

Frontend developers building live-data UIs, editors or heavy animations should read the original for its demos. You can toggle each technique on and off and watch the fps counter, and no summary reproduces that. The code snippets for time-slicing, the priority queue and FLIP are short enough to copy.

The discussion

Most commenters praised the article and its demos, and the arguments happened around it. The biggest fight was whether interactivity is even the web's real performance problem, given the size of JavaScript bundles. A detailed critique took apart several of the article's specific claims, and a side thread argued over whether the text was AI-written.

Bundles and hydration, not interactivity

The top thread blamed huge React and Next.js bundles, whose parsing and hydration take main-thread time no yielding fixes. Commenters on slow connections and cheap laptops agreed; one described cookie dialogs that freeze a half-loaded page for 20 seconds. In the documents-versus-apps argument that followed, one side said most sites could be plain HTML and CSS. The other said the complexity just moves to the server, and SPAs feel better for checkout flows on slow links.

The majority of apps I've optimised over the years have the (often vast) majority of main thread time spent on the bundle parse/hydration (plus obviously network).

— @martinald

I stressed the fact that complaining about the complexity of a page implemented with a JavaScript framework is a red herring, because said complexity doesn't go away by moving it to a server.

— @locknitpicker

Corrections to the article's specifics

chrismorgan wrote the longest critique: Firefox has painted off the main thread since 2017, and the markdown editor needs an incremental parser, not a debounce. He added that batching DOM writes barely matters, while avoiding forced layout does. Others said ArrayBuffers are copied unless transferred, transferControlToOffscreen() beats shipping pixels, and the View Transitions API automates FLIP. On frame rates, some said a steady 60fps beats a missed 120fps target.

No it doesn’t. This is a clear example of your code being slow, because you’re doing the wrong thing. You want an incremental parser of some kind.

— @chrismorgan

It hands the canvas to a worker that draws directly, no pixles over postMessage at all.

— @gioandthemachin

Is the single main thread a design mistake?

Some said the industry learned the lesson of cooperative multitasking by Windows 3.1. They want frameworks to run libraries on separate threads with actors, since developers never move heavy work off the main thread on their own. Defenders replied that one thread acts as a single lock against data races, and every native UI toolkit is single-threaded too. Asked whether the article should have cited decades of scheduling research, one reply said the browser gives developers too few controls to apply it.

Using single thread for everything and expecting that developer will create separate threads for heavy work is obvious approach, but it never worked. Developer doesn't care.

— @vbezhenar

JavaScript's execution model uses cooperative concurrency to avoid a tons of data races, preemptive non-parallel concurrency would make all web development incredibly harder

— @afiori

Virtualized feeds break find-in-page

The deferred-rendering trick drew anger from readers who use the browser's search on Reddit, Bluesky and Outlook Web, where off-screen content doesn't exist. One reply asked how else to handle a DOM with thousands of items. The answer was that the DOM is too heavy, not too long. Outlook nests each email row about 30 divs deep, and real tables scroll thousands of rows fine.

Computers in the 00s could handle inboxes with thousands of emails just fine and scroll through them. Outlook struggles keeping more than 30 in active memory.

— @mschuster91

Was it AI-written?

A few readers called the prose AI slop, citing stock phrases and a Pangram detector score. Others said it read like careful technical writing from ten years ago, and pointed out the author wrote it in Korean and translated it. One commenter suggested the line between machine translation and LLM editing has blurred.

I’ve worked in the field for a long time and see a carefully written, correct, calm article.

— @ricardobeat

Consensus

The article explains main-thread scheduling well, and its interactive demos let readers feel the difference each technique makes. Several commenters said they had learned these lessons piecemeal and were glad to see them in one place.

Dissent

Many argued the article optimizes the wrong thing for most sites, where bloated bundles and hydration cost far more main-thread time than interaction does. chrismorgan's critique also said some of its fixes are weak: debounce loses to a better algorithm, and DOM-write batching matters less than avoiding forced layout.