An image browser I use every day flashes blank cells when you scroll it fast. The obvious suspects are all about images: lazy loading not firing early enough, the decode arriving after the cell is painted, too little rendered ahead of the viewport. I wrote down three hypotheses — grid remount, insufficient overscan, lazy/decode latency — and committed to changing exactly one thing at a time, which is the only reason the answer was legible at the end.

It was none of the image ones. The grid was being destroyed and rebuilt from nothing every time the list grew.

The comment said the opposite of the code

The masonry component was keyed on a value computed from the dataset, and that value included both the last-loaded marker and the length of the list. Infinite scroll appends sixty items at a time; both parts of the key changed on every append; the component unmounted and remounted wholesale. The comment sitting directly above that line read, in effect, a longer list alone must not trigger a re-layout. The key was written to detect filter changes, and infinite scroll satisfies exactly the same condition a filter change does.

Instrumented over a 312-second session across roughly 4,300 assets and 18,742 frames, the correlation was not subtle: 71 remounts against 71 page appends, one to one. Of the 304 blank cells recorded, 72.9% occurred within 0.3 seconds of a remount, median 146 milliseconds; 89.6% within one second.

Splitting the key so it responds to the dataset identity rather than the dataset length took nine lines. Measured the same way afterward: zero remounts across forty page loads, and blanks per API call down from 4.28 to 1.35, a 68% reduction. Nothing else was touched — same overscan, same lazy loading, same thumbnails, same infinite scroll.

The measurement environment nearly cost me the day

This is the part I'd want to read in someone else's post. My first several measurement runs were entirely artificial, and they said the hypothesis was wrong.

When a tab is backgrounded, the browser doesn't merely deprioritise lazy images — it doesn't start them at all, so the source attribute stays empty. The virtualisation library's scroll tracking stops, so it computes the visible window from stale numbers. Its render callback never fires, so infinite scroll halts completely. Every one of those is reasonable behaviour on its own. Together they produce a recording in which the grid remounts zero times and looks perfectly healthy.

That zero wasn't evidence against hypothesis A. It was evidence that the list never grew, so the trigger never occurred. The real numbers only appeared once the tab was in the foreground with a human scrolling it. An automated harness that measures a page nobody is looking at is measuring a different page.

A side note that dissolved on contact

My own impression had been that larger assets blanked more. They don't. The correlation between file size and time-to-paint came out at −0.012, because the server normalises every thumbnail to the same width and they all land around the same few tens of kilobytes. What's actually true is that tall cells leave a bigger hole while they're empty, so the same blank feels worse. Worth checking before optimising: perceived severity and measured frequency are different axes.

And one feature I added, then deleted the same day

Separately, I wired a small read-only panel showing the ingest queue's pending count into the same app. It was cheap — the number already rode along in an existing health response, so no new requests, no backend changes, no write path touched.

Then I looked at what it displayed: zero pending, zero duplicates, zero failed. The cumulative event count is north of 60,000, but items are processed the instant they arrive, so nothing ever queues. The panel could only ever show a recent-activity list, which is not the same thing as a queue state, and not what I'd have built if I'd asked the question first. I reverted it an hour after adding it.

The reasoning that let it through was "it's read-only, so it's free." That was true and irrelevant. Free is a statement about cost, not about worth, and I had checked only the first one.