Performance
Code Optimization
Algorithm efficiency, memory management, and runtime performance optimization
Code Optimization
Writing efficient code improves both performance and maintainability. This covers algorithm optimization, memory management, and JavaScript-specific techniques.
Algorithm Efficiency
Time Complexity Awareness
// O(n²) - Avoid for large datasets
function findDuplicatesSlow(arr: number[]): number[] {
const duplicates: number[] = [];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j] && !duplicates.includes(arr[i])) {
duplicates.push(arr[i]);
}
}
}
return duplicates;
}
// O(n) - Use Set for O(1) lookups
function findDuplicatesFast(arr: number[]): number[] {
const seen = new Set<number>();
const duplicates = new Set<number>();
for (const num of arr) {
if (seen.has(num)) {
duplicates.add(num);
}
seen.add(num);
}
return [...duplicates];
}Efficient Data Structures
// Use Map for frequent lookups
const userMap = new Map<string, User>();
userMap.set(user.id, user);
const user = userMap.get(id); // O(1)
// Use Set for membership checks
const activeIds = new Set<string>(activeUsers.map(u => u.id));
const isActive = activeIds.has(userId); // O(1)
// Use proper indexing for searches
const usersByEmail = new Map(users.map(u => [u.email, u]));
const user = usersByEmail.get(email);Memoization
// Simple memoization
function memoize<T extends (...args: any[]) => any>(fn: T): T {
const cache = new Map<string, ReturnType<T>>();
return ((...args: Parameters<T>) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key)!;
}
const result = fn(...args);
cache.set(key, result);
return result;
}) as T;
}
// With LRU cache for bounded memory
class LRUCache<K, V> {
private cache = new Map<K, V>();
constructor(private maxSize: number) {}
get(key: K): V | undefined {
if (!this.cache.has(key)) return undefined;
// Move to end (most recently used)
const value = this.cache.get(key)!;
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key: K, value: V): void {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.maxSize) {
// Remove oldest (first) entry
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}Memory Management
Avoiding Memory Leaks
// Bad - event listener leak
class Component {
constructor() {
window.addEventListener('resize', this.handleResize);
}
handleResize = () => {
// ...
};
// Missing cleanup!
}
// Good - proper cleanup
class Component {
private abortController = new AbortController();
constructor() {
window.addEventListener('resize', this.handleResize, {
signal: this.abortController.signal,
});
}
handleResize = () => {
// ...
};
destroy() {
this.abortController.abort();
}
}
// React hook pattern
function useWindowResize(callback: () => void) {
useEffect(() => {
const handler = () => callback();
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, [callback]);
}Weak References
// Use WeakMap for object-keyed caches
// Allows garbage collection when key is no longer referenced
const cache = new WeakMap<object, ComputedData>();
function getComputedData(obj: object): ComputedData {
if (!cache.has(obj)) {
cache.set(obj, expensiveComputation(obj));
}
return cache.get(obj)!;
}
// Use WeakSet for membership tracking
const processedItems = new WeakSet<Item>();
function processOnce(item: Item) {
if (processedItems.has(item)) return;
processedItems.add(item);
// Process item...
}Object Pooling
// Reuse objects instead of creating new ones
class ObjectPool<T> {
private pool: T[] = [];
constructor(
private factory: () => T,
private reset: (obj: T) => void,
private initialSize = 10
) {
for (let i = 0; i < initialSize; i++) {
this.pool.push(factory());
}
}
acquire(): T {
return this.pool.pop() ?? this.factory();
}
release(obj: T): void {
this.reset(obj);
this.pool.push(obj);
}
}
// Usage for frequent allocations
const vectorPool = new ObjectPool(
() => ({ x: 0, y: 0 }),
(v) => { v.x = 0; v.y = 0; }
);
function calculateVectors(points: Point[]) {
const vectors = points.map(() => vectorPool.acquire());
// Use vectors...
vectors.forEach(v => vectorPool.release(v));
}String Operations
// Bad - string concatenation in loop
function buildStringSlow(items: string[]): string {
let result = '';
for (const item of items) {
result += item + ', '; // Creates new string each iteration
}
return result;
}
// Good - use array join
function buildStringFast(items: string[]): string {
return items.join(', ');
}
// For complex string building
function buildHTML(items: Item[]): string {
const parts: string[] = [];
parts.push('<ul>');
for (const item of items) {
parts.push(`<li>${item.name}</li>`);
}
parts.push('</ul>');
return parts.join('');
}Array Operations
// Prefer for...of for iteration (when you don't need index)
for (const item of items) {
process(item);
}
// Use forEach for side effects
items.forEach(item => console.log(item));
// Chain methods efficiently
const result = items
.filter(item => item.active)
.map(item => item.value);
// For very large arrays, consider single pass
const result: number[] = [];
for (const item of items) {
if (item.active) {
result.push(item.value);
}
}
// Avoid creating intermediate arrays when possible
// Bad
const sum = items.map(i => i.value).reduce((a, b) => a + b, 0);
// Good
const sum = items.reduce((acc, item) => acc + item.value, 0);Async Optimization
Parallel Execution
// Sequential - slow
async function fetchAllSequential(ids: string[]) {
const results = [];
for (const id of ids) {
results.push(await fetchItem(id));
}
return results;
}
// Parallel - fast
async function fetchAllParallel(ids: string[]) {
return Promise.all(ids.map(id => fetchItem(id)));
}
// Parallel with concurrency limit
async function fetchWithLimit(ids: string[], limit = 5) {
const results: Item[] = [];
const executing: Promise<void>[] = [];
for (const id of ids) {
const promise = fetchItem(id).then(item => {
results.push(item);
});
executing.push(promise);
if (executing.length >= limit) {
await Promise.race(executing);
executing.splice(executing.findIndex(p => p === promise), 1);
}
}
await Promise.all(executing);
return results;
}AbortController for Cancellation
async function fetchWithTimeout(url: string, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}
// Cancel previous request on new one
let currentController: AbortController | null = null;
async function search(query: string) {
currentController?.abort();
currentController = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: currentController.signal,
});
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
return null; // Cancelled, not an error
}
throw error;
}
}Web Workers
Offload heavy computation to background threads.
// worker.ts
self.onmessage = (event: MessageEvent<WorkerInput>) => {
const { data, type } = event.data;
switch (type) {
case 'PROCESS_DATA':
const result = heavyComputation(data);
self.postMessage({ type: 'RESULT', result });
break;
}
};
function heavyComputation(data: number[]): number {
// CPU-intensive work
return data.reduce((sum, n) => sum + Math.sqrt(n), 0);
}
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url));
function processInBackground(data: number[]): Promise<number> {
return new Promise((resolve) => {
worker.onmessage = (event) => {
if (event.data.type === 'RESULT') {
resolve(event.data.result);
}
};
worker.postMessage({ type: 'PROCESS_DATA', data });
});
}Best Practices
Code Optimization Guidelines
- Profile before optimizing - measure, don't guess
- Choose appropriate data structures (Map, Set)
- Avoid premature optimization
- Clean up event listeners and subscriptions
- Use WeakMap/WeakSet for object caches
- Prefer array methods but understand their cost
- Run heavy computation in Web Workers
- Implement request cancellation with AbortController