Performance & Rendering

Understand the browser's render pipeline. Learn how to avoid Layout Thrashing and keep your application running at a buttery-smooth 60 frames per second.

JavaScript8 min readConcept 59 of 63

The 16.6ms Deadline

To achieve a smooth 60 frames per second (fps), the browser must complete all JavaScript execution, style calculations, layout geometry, and painting within 16.6 milliseconds.

If your JavaScript takes 30ms to execute, the browser misses a frame. This is called 'jank'.

The Cost of Layout Thrashing

When you change the DOM (like altering a width or adding a class), the browser invalidates the layout. If you immediately ask the browser for a geometric property (like element.offsetHeight), the browser is forced to pause JavaScript and synchronously recalculate the entire page layout right then and there.

If you do this in a for loop, you trigger **Layout Thrashing**-forcing the browser to recalculate the layout dozens of times in a single frame, destroying performance.

Batching Reads and Writes

The fix is simple: separate your Reads from your Writes.

**Bad (Thrashing):** javascript for (let i = 0; i < items.length; i++) { const h = items[i].offsetHeight; // Read items[i].style.height = (h + 10) + 'px'; // Write (Invalidates layout!) }

**Good (Batched):** javascript // Read everything first const heights = items.map(item => item.offsetHeight); // Then write everything items.forEach((item, i) => { item.style.height = (heights[i] + 10) + 'px'; });

The 16.6ms Timeline

Examine the performance profiles below. Notice how mixing Reads and Writes forces the browser into a vicious cycle of expensive layout recalculations, missing the frame deadline. Batching solves this instantly.

The Render Pipeline

Thrashing
for (let i = 0; i < 10; i++) {
// Read forces sync Layout
let h = box.offsetHeight;
// Write invalidates Layout
box.style.height = (h + 10) + 'px';
}
Batched
// 1. Do all Reads first
let heights = boxes.map(b => b.offsetHeight);

// 2. Do all Writes last
boxes.forEach((b, i) => {
b.style.height = (heights[i] + 10) + 'px';
});
16.6ms DEADLINE
Interleaved (Thrashing)
Read
Write
Layout!
Read
Write
Layout!
Read
Write
Layout!
Frame Dropped (Jank)
Batched (Smooth)
Read
Read
Read
Write
Write
Write
Layout (Once)
Paint
60 FPS ✅
iEvery time you force a synchronous Layout calculation, the browser must halt JavaScript execution. Doing this in a loop guarantees you will miss the 16.6ms frame deadline.

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1What is 'Layout Thrashing'?
  2. 2Why should you use `requestAnimationFrame` instead of `setTimeout` for animations?
  3. 3Which of the following will force a synchronous layout recalculation if the DOM has been mutated?

Remember this

  • The browser strives for 60 frames per second. You have **16.6ms** to complete all JavaScript and rendering work per frame.
  • If you exceed 16.6ms, the browser drops a frame, resulting in noticeable 'jank'.
  • **Layout Thrashing** happens when you interleave DOM reads (like offsetHeight) and DOM writes (like style.height).
  • Prevent thrashing by **batching**: do all your DOM reads first, save the values, and then do all your DOM writes.
  • Always use requestAnimationFrame for JavaScript-driven animations instead of setTimeout.

Done with this concept?

Mark it complete to track your progress. No login needed.