How do you optimize DOM manipulation?
PerformanceThe short answer
DOM manipulation is one of the most expensive operations in the browser. To optimize it, batch your changes, minimize direct DOM access, use document fragments for bulk inserts, avoid layout thrashing, and prefer CSS classes over inline style changes.
Why DOM manipulation is slow
Every time you modify the DOM, the browser may need to:
- Recalculate styles (which elements are affected)
- Reflow (recalculate positions and sizes)
- Repaint (redraw the affected pixels)
A single change is fast. But many changes in a row can cause the browser to redo these steps repeatedly, making the page slow and janky.
Optimization techniques
1. Batch DOM reads and writes separately
// Bad — alternating reads and writes (layout thrashing)elements.forEach((el) => { const height = el.offsetHeight; // read (forces layout) el.style.height = height * 2 + 'px'; // write (invalidates layout)});// Good — all reads first, then all writesconst heights = elements.map((el) => el.offsetHeight);elements.forEach((el, i) => { el.style.height = heights[i] * 2 + 'px';});2. Use document fragments for bulk inserts
// Bad — 1000 individual DOM insertionsfor (let i = 0; i < 1000; i++) { const item = document.createElement('div'); item.textContent = `Item ${i}`; container.appendChild(item); // reflow on each insert}// Good — one insertionconst fragment = document.createDocumentFragment();for (let i = 0; i < 1000; i++) { const item = document.createElement('div'); item.textContent = `Item ${i}`; fragment.appendChild(item);}container.appendChild(fragment); // one reflow3. Use CSS classes instead of inline styles
// Bad — multiple style changes, multiple potential reflowselement.style.width = '100px';element.style.height = '100px';element.style.backgroundColor = 'red';// Good — one class, one reflowelement.classList.add('box-active');4. Minimize DOM queries
// Bad — queries the DOM on every iterationfor (let i = 0; i < 100; i++) { document.querySelector('#counter').textContent = i;}// Good — cache the referenceconst counter = document.querySelector('#counter');for (let i = 0; i < 100; i++) { counter.textContent = i;}5. Use event delegation
Instead of adding event listeners to every child element, add one to the parent:
// Bad — 100 event listenersitems.forEach((item) => { item.addEventListener('click', handleClick);});// Good — 1 event listenerlist.addEventListener('click', (e) => { if (e.target.matches('.item')) { handleClick(e); }});6. Use requestAnimationFrame for visual updates
// Bad — update might happen mid-frame, causing jankwindow.addEventListener('scroll', () => { element.style.transform = `translateY(${window.scrollY}px)`;});// Good — update aligned with the browser's refresh cyclewindow.addEventListener('scroll', () => { requestAnimationFrame(() => { element.style.transform = `translateY(${window.scrollY}px)`; });});Interview Tip
Focus on the most impactful techniques: batching reads and writes (avoid layout thrashing), document fragments for bulk inserts, and event delegation. These are the ones that make the biggest difference in real applications. If you can explain why each technique helps (fewer reflows, fewer event listeners, fewer DOM queries), it shows you understand the underlying browser mechanics.
Why interviewers ask this
DOM performance is critical for smooth user experiences. Interviewers ask this to see if you know the common bottlenecks and how to avoid them. Even in React applications where you do not manipulate the DOM directly, understanding these principles helps you write better components and debug performance issues.