How to Improve JavaScript Code's Performance
Source: Dev.to
1. Understand the JavaScript Execution Model
Modern JavaScript engines (V8, SpiderMonkey, JavaScriptCore) rely on Just‑In‑Time (JIT) compilation, inline caching, hidden classes/shapes, and speculative optimizations.
Avoid De‑Optimization Triggers
Engines optimize functions based on observed types. Changing types mid‑execution forces de‑optimization.
// Avoid this
function sum(a, b) {
return a + b;
}
sum(1, 2); // optimized for numbers
sum("1", "2"); // de‑optimizes
// Do this
function sum(a, b) {
a = Number(a);
b = Number(b);
return a + b;
}
2. Shape Stability and Object Access
JavaScript objects use hidden classes. Mutating object structure dynamically prevents property‑access optimization. When iterating over large datasets, stable shapes can yield 20–50 % performance improvements.
// Avoid this
const user = {};
user.name = "Alice";
user.age = 30;
user.isAdmin = true;
// Do this
const user = {
name: "Alice",
age: 30,
isAdmin: true
};
3. Minimize Garbage Collection Pressure
Garbage collection (GC) pauses execution. High allocation rates cause frequent GC cycles. Avoid unnecessary allocations in hot paths.
// Excessive allocation
function process(items) {
return items.map(item => ({
id: item.id,
value: item.value * 2
}));
}
// Object reuse
function process(items) {
const result = new Array(items.length);
for (let i = 0; i < items.length; i++) {
const item = items[i];
result[i] = {
id: item.id,
value: item.value * 2
};
}
return result;
}
Loop vs. Functional Methods
// Using map / filter / reduce
const total = items
.filter(p => p > 10)
.map(p => p * 1.2)
.reduce((a, b) => a + b, 0);
// Optimized loop
let total = 0;
for (let i = 0; i < items.length; i++) {
const p = items[i];
if (p > 10) {
total += p * 1.2;
}
}
5. Async Performance: Avoid Accidental Serialization
async/await can introduce hidden bottlenecks when misused. Proper parallelism can reduce execution time from O(n × latency) to O(max latency).
// Serialized execution
async function fetchAll(urls) {
const results = [];
for (const url of urls) {
results.push(await fetch(url));
}
return results;
}
// Parallel execution
async function fetchAll(urls) {
return Promise.all(urls.map(fetch));
}
6. Memoization and Cache Invalidation
Avoid recomputation for deterministic, expensive functions. Memoization must be paired with cache‑size limits and clear invalidation rules; otherwise memory leaks offset gains.
const cache = new Map();
function expensiveCalc(n) {
if (cache.has(n)) return cache.get(n);
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i);
}
cache.set(n, result);
return result;
}
DOM Batching (Read/Write Separation)
// Bad: interleaved reads and writes
elements.forEach(el => {
el.style.width = el.offsetWidth + 10 + "px";
});
// Good: batch reads then writes
const widths = elements.map(el => el.offsetWidth);
elements.forEach((el, i) => {
el.style.width = widths[i] + 10 + "px";
});
8. Measure, Don’t Guess
Use the available tools and APIs to measure performance. Optimization without measurement is cargo‑cult engineering.
performance.now()- Chrome DevTools Performance tab
- Node.js
--prof - Flamegraphs
const start = performance.now();
// critical code
const end = performance.now();
console.log(`Execution: ${end - start}ms`);
Your code either cooperates with these systems or actively fights them. Stable types, stable shapes, controlled memory allocation, and intentional async behavior allow engines to optimize aggressively.