Performance optimization is critical for creating fast, responsive web applications. This guide covers techniques, patterns, and best practices for optimizing JavaScript performance.
Measuring Performance #
Performance API #
// Measure execution time
const start = performance.now();
// Your code here
for (let i = 0; i < 1000000; i++) {
// ...
}
const end = performance.now();
console.log(`Execution time: ${end - start}ms`);console.time() #
console.time('operation');
// Your code
someOperation();
console.timeEnd('operation');
// operation: 123.456ms
Performance Marks #
performance.mark('start');
// Your code
doSomething();
performance.mark('end');
performance.measure('operation', 'start', 'end');
const measure = performance.getEntriesByName('operation')[0];
console.log(`Duration: ${measure.duration}ms`);Algorithm Optimization #
Time Complexity #
// Bad - O(n²)
function hasDuplicate(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) return true;
}
}
return false;
}
// Good - O(n)
function hasDuplicate(arr) {
const seen = new Set();
for (const item of arr) {
if (seen.has(item)) return true;
seen.add(item);
}
return false;
}Memoization #
// Without memoization - exponential time
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// With memoization - linear time
function fibonacci(n, memo = {}) {
if (n in memo) return memo[n];
if (n <= 1) return n;
memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
return memo[n];
}
// Using closure
function createFibonacci() {
const cache = {};
return function fib(n) {
if (n in cache) return cache[n];
if (n <= 1) return n;
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
};
}
const fib = createFibonacci();
console.log(fib(40)); // Fast!
Debouncing #
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Usage
const handleSearch = debounce((query) => {
console.log('Searching for:', query);
}, 300);
searchInput.addEventListener('input', (e) => {
handleSearch(e.target.value);
});Throttling #
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// Usage
const handleScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100);
window.addEventListener('scroll', handleScroll);Loop Optimization #
Cache Length #
// Bad
for (let i = 0; i < arr.length; i++) {
// arr.length computed every iteration
}
// Good
for (let i = 0, len = arr.length; i < len; i++) {
// Length cached
}Use Appropriate Loop #
const arr = [1, 2, 3, 4, 5];
// forEach - No break/continue
arr.forEach(item => console.log(item));
// for...of - Can break/continue
for (const item of arr) {
if (item === 3) break;
console.log(item);
}
// for - Fastest for numeric iteration
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// while - Best for unknown iterations
let i = 0;
while (i < arr.length) {
console.log(arr[i++]);
}Avoid Array Methods in Hot Paths #
// Bad - Creates new array
function sumSquares(arr) {
return arr
.map(x => x * x)
.reduce((sum, x) => sum + x, 0);
}
// Good - Single pass
function sumSquares(arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i] * arr[i];
}
return sum;
}Object Optimization #
Object Creation #
// Bad - Dynamic property addition
const obj = {};
obj.a = 1;
obj.b = 2;
obj.c = 3;
// Good - All properties defined upfront
const obj = { a: 1, b: 2, c: 3 };
// Good - Constructor
function Point(x, y) {
this.x = x;
this.y = y;
}
const p = new Point(1, 2);Object.freeze() for Constants #
// Prevents modifications and enables optimizations
const CONFIG = Object.freeze({
API_URL: 'https://api.example.com',
TIMEOUT: 5000,
MAX_RETRIES: 3
});Use Maps for Frequent Lookups #
// Bad - Object lookup
const cache = {};
cache[key] = value;
const result = cache[key];
// Good - Map is optimized for frequent additions/deletions
const cache = new Map();
cache.set(key, value);
const result = cache.get(key);String Optimization #
Avoid String Concatenation in Loops #
// Bad
let str = '';
for (let i = 0; i < 1000; i++) {
str += i; // Creates new string each iteration
}
// Good
const arr = [];
for (let i = 0; i < 1000; i++) {
arr.push(i);
}
const str = arr.join('');
// Best - Template literal for known strings
const str = `${val1} ${val2} ${val3}`;Use String Methods Wisely #
// Bad - Multiple operations
const result = str
.toLowerCase()
.trim()
.split(' ')
.join('-');
// Better - Regex for complex transformations
const result = str.toLowerCase().trim().replace(/\s+/g, '-');Array Optimization #
Pre-allocate Arrays #
// Bad - Array grows dynamically
const arr = [];
for (let i = 0; i < 1000; i++) {
arr.push(i);
}
// Good - Pre-allocate size
const arr = new Array(1000);
for (let i = 0; i < 1000; i++) {
arr[i] = i;
}Avoid Array Holes #
// Bad - Creates sparse array
const arr = [];
arr[1000] = 1;
// Good - Dense array
const arr = new Array(1001).fill(0);
arr[1000] = 1;Use Typed Arrays for Numbers #
// Regular array
const arr = new Array(1000000);
// Typed array - More memory efficient
const arr = new Float64Array(1000000);
const arr = new Int32Array(1000000);Function Optimization #
Avoid Creating Functions in Loops #
// Bad
for (let i = 0; i < arr.length; i++) {
arr[i].addEventListener('click', function() {
console.log(i);
});
}
// Good
function handleClick(i) {
return function() {
console.log(i);
};
}
for (let i = 0; i < arr.length; i++) {
arr[i].addEventListener('click', handleClick(i));
}
// Best - Single handler
function handleClick(e) {
const index = parseInt(e.target.dataset.index);
console.log(index);
}
for (let i = 0; i < arr.length; i++) {
arr[i].dataset.index = i;
arr[i].addEventListener('click', handleClick);
}Inline Small Functions #
// Bad - Function call overhead
function square(x) {
return x * x;
}
const result = arr.map(x => square(x));
// Good - Inlined
const result = arr.map(x => x * x);Use Rest Parameters Carefully #
// Slower - Creates array
function sum(...args) {
return args.reduce((a, b) => a + b, 0);
}
// Faster - Fixed parameters
function sum(a, b, c) {
return a + b + c;
}DOM Optimization #
Batch DOM Updates #
// Bad - Multiple reflows
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
div.textContent = i;
document.body.appendChild(div); // Reflow each time
}
// Good - Single reflow
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
div.textContent = i;
fragment.appendChild(div);
}
document.body.appendChild(fragment); // Single reflow
Cache DOM Queries #
// Bad
for (let i = 0; i < 100; i++) {
document.getElementById('container').innerHTML += i;
}
// Good
const container = document.getElementById('container');
for (let i = 0; i < 100; i++) {
container.innerHTML += i;
}
// Best
const container = document.getElementById('container');
const parts = [];
for (let i = 0; i < 100; i++) {
parts.push(i);
}
container.innerHTML = parts.join('');Use Event Delegation #
// Bad - Multiple listeners
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// Good - Single listener
document.querySelector('.container').addEventListener('click', (e) => {
if (e.target.classList.contains('item')) {
handleClick(e);
}
});Memory Optimization #
Avoid Memory Leaks #
// Bad - Closure keeps reference
function createLeak() {
const bigArray = new Array(1000000).fill('leak');
return function() {
// bigArray is kept in memory
console.log('leaked');
};
}
// Good - Release references
function noLeak() {
let bigArray = new Array(1000000).fill('no leak');
setTimeout(() => {
bigArray = null; // Allow GC
}, 1000);
}Clear Event Listeners #
// Bad - Listener keeps reference
const element = document.getElementById('myElement');
element.addEventListener('click', handleClick);
// Good - Remove when done
element.removeEventListener('click', handleClick);
// Best - Use AbortController
const controller = new AbortController();
element.addEventListener('click', handleClick, { signal: controller.signal });
// Later
controller.abort(); // Removes all listeners
WeakMap for Metadata #
// Bad - Prevents GC
const metadata = new Map();
function setMetadata(obj, data) {
metadata.set(obj, data); // obj never GC'd
}
// Good - Allows GC
const metadata = new WeakMap();
function setMetadata(obj, data) {
metadata.set(obj, data); // obj can be GC'd
}Async Optimization #
Use Promise.all for Parallel Operations #
// Bad - Sequential
async function fetchAll(urls) {
const results = [];
for (const url of urls) {
results.push(await fetch(url));
}
return results;
}
// Good - Parallel
async function fetchAll(urls) {
return Promise.all(urls.map(url => fetch(url)));
}Lazy Loading #
// Load modules on demand
async function loadFeature() {
const module = await import('./feature.js');
module.init();
}
// Lazy load images
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});Code Splitting #
Dynamic Imports #
// Load code only when needed
button.addEventListener('click', async () => {
const module = await import('./heavy-module.js');
module.doSomething();
});Tree Shaking #
// Named exports enable tree shaking
export function used() { }
export function unused() { } // Will be removed
// Usage
import { used } from './module.js'; // Only 'used' bundled
Worker Threads #
Web Workers for Heavy Computation #
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (e) => {
console.log('Result:', e.data);
};
// worker.js
self.onmessage = (e) => {
const result = processLargeArray(e.data.data);
self.postMessage(result);
};
function processLargeArray(arr) {
// Heavy computation
return arr.map(x => x * 2);
}Best Practices #
- Measure first - Profile before optimizing
- Optimize bottlenecks - Focus on hot paths
- Use appropriate data structures - Map/Set vs Object/Array
- Batch DOM updates - Minimize reflows
- Cache computed values - Memoization
- Debounce/throttle events - Reduce function calls
- Lazy load - Load code on demand
- Use Web Workers - Offload heavy computation
- Minimize object creation - Reuse when possible
- Profile regularly - Performance can regress
Performance optimization requires measuring, identifying bottlenecks, and applying appropriate techniques. Focus on algorithmic improvements first, then micro-optimizations.