All posts
Engineering

Rendering 40,000 threads without dropping a frame

Virtualization gets you most of the way. The last stretch was avoiding layout thrash on a list where every row can change height.

SO
Sam Okafor
Client Engineering
11 min read

A mail list is a deceptively hard rendering problem. Rows are variable height, the data updates while you scroll, and users expect the scrollbar to mean something.

Windowing is table stakes

We render roughly three viewports of rows and recycle the rest. Nothing surprising there. The surprising part is what breaks afterwards.

Height estimation is the whole game

With fixed rows, virtualization is arithmetic. With variable rows, every wrong estimate shifts the scroll position of everything below it, and the user watches their place drift while they read.

We measure rows once on first paint, cache the result keyed by thread id, and seed the estimate for unmeasured rows from a running median of the same label. Threads with attachments and long subjects get their own bucket, because they were dragging the median in the wrong direction.

Never resize during scroll

The real fix was refusing to apply measured heights while a scroll is in flight. Changes queue up and flush on idle. It means a row can be a few pixels off for one frame, which nobody notices, instead of the list jumping under a thumb, which everybody notices.

Where the remaining time goes

After all of that, the profile is dominated by string work: highlighting search matches and sanitizing HTML previews. Both moved to a worker. The main thread now does layout and nothing else, and the list holds its frame budget on a five-year-old laptop.

Parrot Mail

Your inbox, finally under control.

Connect unlimited accounts, free for 30 days. No credit card required — cancel anytime.

SOC 2 Type IIEncrypted at rest & in transit