# Layout thrashing eliminated

- **Pattern:** `ab-002014` (`performance-core.script-style-efficiency.layout-thrashing`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002014
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002014)

## Why it matters

Interleaving DOM reads and writes in a loop forces the browser to recalculate layout after every write before it can serve the next read — called "layout thrashing" (ISO-25010 time-behaviour). Reading `element.offsetHeight` after setting `element.style.height` on 100 elements triggers 100 synchronous layout recalculations instead of one. On a 60Hz display, each recalculation costs approximately 16ms of budget. One thrashing loop can consume the entire frame budget, causing the browser to drop frames and producing visible jank.

## Severity rationale

Low because layout thrashing causes visible jank and dropped frames during the specific interaction that triggers it, but does not affect baseline performance outside those code paths.

## Remediation

Batch all DOM reads before any DOM writes. Read from the DOM, store the values, then write back in a separate pass:

```ts
// Bad: interleaved read-write causes layout thrashing
for (const el of elements) {
  el.style.height = el.scrollHeight + 'px'  // read then write on every iteration
}

// Good: batch reads first, then batch writes
const heights = Array.from(elements).map(el => el.scrollHeight)  // all reads
elements.forEach((el, i) => { el.style.height = heights[i] + 'px' })  // all writes
```

For animation-related mutations, wrap batched writes in `requestAnimationFrame`:
```ts
requestAnimationFrame(() => {
  const updates = Array.from(elements).map(el => ({ el, height: el.scrollHeight }))
  updates.forEach(({ el, height }) => { el.style.height = `${height}px` })
})
```

Use the FastDOM library or Chrome DevTools Performance tab → Layout to identify thrashing code paths.

## Detection

- **ID:** `layout-thrashing`
- **Severity:** `low`
- **What to look for:** Count all relevant instances and enumerate each. Review JavaScript that reads and writes to the DOM. Look for patterns like `element.offsetHeight` followed immediately by `element.style.height = ...`. Check for loops that interleave reads and writes. Examine event handlers for synchronized read-write operations.
- **Pass criteria:** DOM read operations are batched before DOM writes. `requestAnimationFrame` is used for coordinating multiple DOM mutations. Loops batch reads, then batch writes separately.
- **Fail criteria:** DOM reads and writes are interleaved in loops or event handlers, causing the browser to recalculate layout repeatedly (layout thrashing). Code like `for (let el of elements) { read offsetHeight; write style; }`.
- **Skip (N/A) when:** The project has minimal DOM manipulation or no dynamic updates.
- **Detail on fail:** Provide an example of thrashing. Example: `"Loop over 100 items: element.offsetHeight read, then style.height written for each. Layout recalculated 100 times in 300ms instead of once"` or `"Scroll event handler reads clientHeight (triggers layout) then writes transform (triggers paint) multiple times"`.
- **Remediation:** Batch DOM reads and writes:

  Bad (layout thrashing):
  ```ts
  for (let el of elements) {
    el.style.height = el.scrollHeight + 'px'  // read then write, repeated
  }
  ```

  Good (batched):
  ```ts
  const heights = elements.map(el => el.scrollHeight)  // batch all reads
  elements.forEach((el, i) => el.style.height = heights[i] + 'px')  // batch all writes
  ```

  Or with requestAnimationFrame:
  ```ts
  function updateElements() {
    requestAnimationFrame(() => {
      const updates = elements.map(el => ({ el, height: el.scrollHeight }))
      updates.forEach(({ el, height }) => el.style.height = height + 'px')
    })
  }
  ```

## External references

- iso-25010 performance-efficiency.time-behaviour

Taxons: performance

HTML version: https://auditbuffet.com/patterns/ab-002014
