JavaScript Performance Patterns
Table of Contents
Runtime performance micro-patterns for JavaScript hot paths. These patterns matter most in tight loops, frequent callbacks (scroll, resize, animation frames), and data-heavy operations. They apply to any JavaScript environment — React, Vue, vanilla, Node.js.
When to Use
Reference these patterns when:
- Profiling reveals a hot function or tight loop
- Processing large datasets (1,000+ items)
- Handling high-frequency events (scroll, mousemove, resize)
- Optimizing build-time or server-side scripts
- Reviewing code for performance in critical paths
Instructions
- Apply these patterns only in measured hot paths — code that runs frequently or processes large datasets. Don't apply them to cold code paths where readability is more important than nanosecond gains.
Details
Overview
Micro-optimizations are not a substitute for algorithmic improvements. Address the algorithm first (O(n^2) to O(n), removing waterfalls, reducing DOM mutations). Once the algorithm is right, these patterns squeeze additional performance from hot paths.
1. Use Set and Map for Lookups
Impact: HIGH for large collections — O(1) vs O(n) per lookup.
Array methods like .includes(), .find(), and .indexOf() scan linearly. For repeated lookups against the same collection, convert to Set or Map first.
Avoid — O(n) per check:
Prefer — O(1) per check:
For key-value lookups, use Map instead of scanning an array of objects:
2. Batch DOM Reads and Writes
Impact: HIGH — Prevents layout thrashing.
Interleaving DOM reads (e.g., offsetHeight, getBoundingClientRect) with DOM writes (e.g., style.height = ...) forces the browser to recalculate layout multiple times. Batch all reads first, then all writes.
Avoid — layout thrashing (read/write/read/write):
Prefer — batched reads then writes:
For complex cases, use requestAnimationFrame to defer writes to the next frame, or use a library like fastdom.
CSS class approach — single reflow:
3. Cache Property Access in Tight Loops
Impact: MEDIUM — Reduces repeated property resolution.
Accessing deeply nested properties or array .length in every iteration adds overhead in tight loops.
Avoid:
Prefer:
This matters for arrays with 10,000+ items or when called at 60fps. For small arrays or infrequent calls, the readable version is fine.
4. Memoize Expensive Function Results
Impact: MEDIUM-HIGH — Avoids recomputing the same result.
When a pure function is called repeatedly with the same arguments, cache the result.
Simple single-value cache:
Multi-key cache with Map:
For caches that can grow unbounded, use an LRU strategy or WeakMap for object keys.
5. Combine Iterations Over the Same Data
Impact: MEDIUM — Single pass instead of multiple.
Chaining .filter().map().reduce() creates intermediate arrays and iterates the data multiple times. For large arrays in hot paths, combine into a single loop.
Avoid — 3 iterations, 2 intermediate arrays:
Prefer — single pass:
For small arrays (< 100 items), the chained version is fine and more readable. Optimize only when profiling shows it matters.
6. Short-Circuit with Length Checks First
Impact: LOW-MEDIUM — Avoids expensive operations on empty inputs.
Before running expensive comparisons or transformations, check if the input is empty.
7. Return Early to Skip Unnecessary Work
Impact: LOW-MEDIUM — Reduces average-case execution.
Structure functions to exit as soon as possible for common non-matching cases.
Avoid — always does full work:
Prefer — exits early:
8. Hoist RegExp and Constant Creation Outside Loops
Impact: LOW-MEDIUM — Avoids repeated compilation.
Creating RegExp objects or constant values inside loops or frequently-called functions wastes CPU.
Avoid — compiles regex 10,000 times:
Prefer — compile once:
9. Use toSorted(), toReversed(), toSpliced() for Immutability
Impact: LOW — Correct immutability without manual copying.
The new non-mutating array methods avoid the [...arr].sort() pattern and communicate intent more clearly.
Avoid — manual copy then mutate:
Prefer — non-mutating methods:
These are available in all modern browsers and Node.js 20+.
10. Use requestAnimationFrame for Visual Updates
Impact: MEDIUM — Syncs with the browser's render cycle.
DOM updates triggered outside the rendering cycle (from timers, event handlers, etc.) can cause jank. Batch visual updates inside requestAnimationFrame.
Avoid — updates outside render cycle:
Prefer — synced to render:
11. Use structuredClone for Deep Copies
Impact: LOW — Correct deep cloning without libraries.
structuredClone() handles circular references, typed arrays, Dates, RegExps, Maps, and Sets — unlike JSON.parse(JSON.stringify()).
Note: structuredClone cannot clone functions or DOM nodes. For those cases, implement a custom clone.
12. Prefer Map Over Plain Objects for Dynamic Keys
Impact: LOW-MEDIUM — Better performance for frequent additions/deletions.
V8 optimizes plain objects for static shapes. When keys are added and removed dynamically (caches, counters, registries), Map provides consistently better performance.
Source
Patterns from patterns.dev — JavaScript performance guidance for the broader web engineering community.


