Steven's Knowledge
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

  1. Profile before optimizing - measure, don't guess
  2. Choose appropriate data structures (Map, Set)
  3. Avoid premature optimization
  4. Clean up event listeners and subscriptions
  5. Use WeakMap/WeakSet for object caches
  6. Prefer array methods but understand their cost
  7. Run heavy computation in Web Workers
  8. Implement request cancellation with AbortController

On this page