JavaScript Performance at the Page Level
⚙️ JavaScript Performance at the Page Level
The Problem: Naive High-Frequency Handlers
A scroll event can fire dozens of times per second. If a handler does anything expensive — DOM queries, layout reads, re-renders — on every single fire, that's dozens of expensive operations competing for the exact main thread Chapter 4 already flagged as the INP bottleneck.
⏸️ Debounce and Throttle, Revisited
Debounce
Wait until events stop firing for a pause, then run once. Good for "final state" work — a resize handler recalculating layout only once the user has actually finished resizing.
Throttle
Run at most once per fixed interval, no matter how many events fire. Good for "ongoing feedback" work — a scroll-based progress bar that needs periodic updates during the scroll, not just after it.
Same concepts from JS Advanced — but here the stakes are the whole page's measured responsiveness, not one component quietly glitching.
Layout Thrashing
A particularly damaging pattern: reading a layout property (offsetHeight, getBoundingClientRect()) then writing a style change, then reading again — interleaved in a loop. Each read after a write forces the browser to synchronously recalculate layout, since it can no longer batch reads once a write has invalidated them:
Interleaved vs Batched
Thrashing (Interleaved)
Read height, write a new width, read height again, write again — each read forces a fresh, synchronous layout recalculation.
Batched
Read every needed value first, then perform every write afterward — the browser recalculates layout once, not once per element.
requestAnimationFrame
For anything visual, wrapping DOM writes in requestAnimationFrame aligns them with the browser's actual paint cycle instead of fighting it — and pairs naturally with throttling:
rAF-Throttled Scroll Handler
📦 Third-Party Scripts, Revisited
Chapter 4 flagged third-party scripts as a common, easy-to-overlook INP cost. This chapter is where something actually gets done about it: loading with async/defer instead of blocking, and using a facade pattern for expensive embeds — a static thumbnail image that only loads the real player's JS on click, instead of loading it upfront for every visitor regardless of whether they ever interact with it.
💻 Coding Challenges
Challenge 1: Debounce a Resize Handler
Write a debounced version of a resize event handler that calls recalculateLayout() only after resizing has stopped for 200ms.
Goal: Practice implementing debounce for a "final state" use case at the page level.
Challenge 2: Fix a Layout-Thrashing Loop
Given a loop that reads el.offsetHeight and immediately writes el.style.width for each of 50 elements in sequence, rewrite it to batch all reads before all writes.
Goal: Practice eliminating layout thrashing by separating read and write phases.
Challenge 3: Build a Facade for an Embedded Video
Write HTML/JS for a video embed that initially shows only a clickable thumbnail image, and only inserts the real (heavy) video iframe into the DOM after the user clicks it.
Goal: Practice implementing the facade pattern for a genuinely expensive third-party embed.
Choosing the wrong one doesn't just make something "less optimal" — it produces a subtly broken experience. Debouncing a scroll-based progress indicator means the user never sees continuous feedback while scrolling, defeating the entire point of a progress bar — they'd only see it update once scrolling stops. Throttling a search-as-you-type input, meanwhile, fires periodically during typing, triggering wasted, flickering intermediate searches for partial queries the user never intended to search — debounce, waiting for the pause in typing, is what that interaction actually wants. Match the technique to whether the interaction needs ongoing feedback (throttle) or a single final result (debounce), not by habit or whichever one comes to mind first.
🎯 What's Next
With the JavaScript itself under control, the next chapter turns to proving it actually worked: Measuring & Monitoring — lab data vs field data, the web-vitals library, and setting a performance budget.