How do you optimize DOM manipulation?
The 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
1// Bad — alternating reads and writes (layout thrashing)2elements.forEach((el) => {3 const height = el.offsetHeight; // read (forces layout)4 el.style.height = height * 2 + 'px'; // write (invalidates layout)5});67// Good — all reads first, then all writes8const heights = elements.map((el) => el.offsetHeight);9elements.forEach((el, i) => {10 el.style.height = heights[i] * 2 + 'px';11});
2. Use document fragments for bulk inserts
1// Bad — 1000 individual DOM insertions2for (let i = 0; i < 1000; i++) {3 const item = document.createElement('div');4 item.textContent = `Item ${i}`;5 container.appendChild(item); // reflow on each insert6}78// Good — one insertion9const fragment = document.createDocumentFragment();10for (let i = 0; i < 1000; i++) {11 const item = document.createElement('div');12 item.textContent = `Item ${i}`;13 fragment.appendChild(item);14}15container.appendChild(fragment); // one reflow
3. Use CSS classes instead of inline styles
1// Bad — multiple style changes, multiple potential reflows2element.style.width = '100px';3element.style.height = '100px';4element.style.backgroundColor = 'red';56// Good — one class, one reflow7element.classList.add('box-active');
4. Minimize DOM queries
1// Bad — queries the DOM on every iteration2for (let i = 0; i < 100; i++) {3 document.querySelector('#counter').textContent = i;4}56// Good — cache the reference7const counter = document.querySelector('#counter');8for (let i = 0; i < 100; i++) {9 counter.textContent = i;10}
5. Use event delegation
Instead of adding event listeners to every child element, add one to the parent:
1// Bad — 100 event listeners2items.forEach((item) => {3 item.addEventListener('click', handleClick);4});56// Good — 1 event listener7list.addEventListener('click', (e) => {8 if (e.target.matches('.item')) {9 handleClick(e);10 }11});
6. Use requestAnimationFrame for visual updates
1// Bad — update might happen mid-frame, causing jank2window.addEventListener('scroll', () => {3 element.style.transform = `translateY(${window.scrollY}px)`;4});56// Good — update aligned with the browser's refresh cycle7window.addEventListener('scroll', () => {8 requestAnimationFrame(() => {9 element.style.transform = `translateY(${window.scrollY}px)`;10 });11});
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.