You will write JavaScript that avoids repeated expensive work, especially DOM queries and layout reads, so interactions stay responsive.
What it is
JavaScript performance is the practice of reducing work done on the browser's main thread. A useful mental model is that the browser must parse, execute, style, layout, and paint. Code that repeatedly asks the DOM for information or changes styles one element at a time can force extra layout and paint work. Related terms include reflow, repaint, layout thrashing, batching, and caching.
Why it matters
- It keeps typing, searching, and filtering smooth on large lists.
- It prevents visible jank during animations or frequent updates.
- It reduces CPU and battery usage on slower devices.
- It makes code easier to reason about by separating reads from writes.
Syntax or steps
- Measure first. Use browser DevTools to find slow functions before optimizing.
- Cache DOM references outside loops, such as
document.getElementByIdorlist.children. - Batch reads and writes: collect data first, then update the DOM.
- Use
requestAnimationFramewhen updating visual state repeatedly.
Example
const list = document.createElement('ul');
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = 'item ' + i;
list.appendChild(li);
}
document.body.appendChild(list);
const items = Array.from(list.children);
function highlightMatches(query) {
const lower = query.toLowerCase();
const matchSet = new Set();
for (const item of items) {
if (item.textContent.toLowerCase().includes(lower)) {
matchSet.add(item);
}
}
for (const item of items) {
item.classList.toggle('match', matchSet.has(item));
}
}
highlightMatches('item 5');
This example creates 1000 list items, then caches the child elements in items. The first loop only reads text and builds a Set of matches. The second loop only writes class changes. This avoids querying the DOM inside the loop and avoids mixing reads and writes, which can trigger repeated layout work.
Common mistakes
- Calling
document.querySelectorinside a loop. Fix it by caching the element or list once. - Reading layout properties like
offsetHeightafter writing styles in the same loop. Fix it by batching all reads first, then all writes. - Optimizing before measuring. Fix it by using the Performance panel to confirm the bottleneck.
- Using
innerHTMLrepeatedly for small updates. Fix it by updating only the needed nodes or batching changes.
When to use it
Use caching and batching when the DOM is large or updated frequently. For tiny one-off changes, simple direct DOM updates are often clearer and fast enough.
| Approach | Use when | Watch out for |
|---|---|---|
| Cache DOM references and batch updates | Large lists, search filters, animations | Stale references if the DOM changes |
| Query and update immediately | Small, rare, one-off changes | Repeated layout work inside loops |
Practice
- Guided exercise: refactor this pattern so the DOM is queried once:
for (let i = 0; i < 1000; i++) { document.querySelector('.row').classList.add('active'); }. Expected result: one query, then one class update. - Challenge: update 500 rows by adding
evenoroddclasses. Hint: build an array of changes first, then apply them in a second loop.
Quick check
Question: Why is reading offsetHeight inside a loop that also changes styles often slow?
Answer: It forces the browser to compute layout after each write, causing repeated reflows.
Summary
Fast JavaScript often comes from doing less work, not clever tricks. Cache expensive lookups, batch DOM reads and writes, and measure before optimizing.