JS

Common Patterns

JavaScript · 8 entries

Debounce

syntax
function debounce(fn, delay) { ... }
example
function debounce(fn, delay) {
  let timerId;
  return function (...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Usage: only fire after user stops typing for 300ms
const searchInput = document.querySelector("#search");
searchInput.addEventListener("input",
  debounce((e) => {
    console.log("Searching:", e.target.value);
  }, 300)
);

Note Debouncing delays execution until activity stops for a given period. Ideal for search inputs, window resize handlers, and form validation.

Throttle

syntax
function throttle(fn, interval) { ... }
example
function throttle(fn, interval) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// Usage: fire at most once per 200ms during scroll
window.addEventListener("scroll",
  throttle(() => {
    console.log("Scroll position:", window.scrollY);
  }, 200)
);

Note Throttling ensures a function runs at most once per interval, even if triggered constantly. Use for scroll, mousemove, and resize events.

Memoization

syntax
function memoize(fn) { ... }
example
function memoize(fn) {
  const cache = new Map();
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const expensiveCalc = memoize((n) => {
  console.log("Computing...");
  return n ** 2 + Math.sqrt(n);
});

console.log(expensiveCalc(100)); // "Computing..." then 10010
console.log(expensiveCalc(100)); // 10010 (cached, no log)

Note Caches results for repeated calls with the same arguments. Use JSON.stringify for simple keys. For complex args, consider a WeakMap-based approach.

Currying

syntax
const curried = (a) => (b) => (c) => result;
example
const createFormatter = (currency) => (locale) => (amount) =>
  new Intl.NumberFormat(locale, {
    style: "currency", currency
  }).format(amount);

const formatUSD = createFormatter("USD")("en-US");
const formatEUR = createFormatter("EUR")("de-DE");

console.log(formatUSD(1234.5));  // "$1,234.50"
console.log(formatEUR(1234.5));  // "1.234,50 €"

Note Currying transforms a function of multiple arguments into a chain of single-argument functions. Enables partial application and reusable configurations.

Singleton Pattern

syntax
class Singleton { static #instance; static getInstance() {} }
example
class Database {
  static #instance;
  #connection;

  constructor() {
    if (Database.#instance) {
      throw new Error("Use Database.getInstance()");
    }
    this.#connection = "connected";
  }

  static getInstance() {
    if (!Database.#instance) {
      Database.#instance = new Database();
    }
    return Database.#instance;
  }

  query(sql) {
    console.log(`[${this.#connection}] Executing: ${sql}`);
  }
}

const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true

Note Ensures only one instance of a class exists. In modern JavaScript, ES modules are natural singletons -- an exported object is shared across all importers.

Observer / Event Emitter Pattern

syntax
class EventEmitter { on(event, handler) {} emit(event, data) {} }
example
class EventBus {
  #handlers = new Map();

  on(event, handler) {
    if (!this.#handlers.has(event)) {
      this.#handlers.set(event, new Set());
    }
    this.#handlers.get(event).add(handler);
    return () => this.off(event, handler); // unsubscribe fn
  }

  off(event, handler) {
    this.#handlers.get(event)?.delete(handler);
  }

  emit(event, data) {
    this.#handlers.get(event)?.forEach(fn => fn(data));
  }
}

const bus = new EventBus();
const unsub = bus.on("user:login", (user) => console.log(`Welcome, ${user}`));
bus.emit("user:login", "Ava"); // "Welcome, Ava"
unsub(); // unsubscribes

Note The observer pattern decouples event producers from consumers. Returning an unsubscribe function from on() prevents memory leaks.

Pipe / Compose

syntax
const pipe = (...fns) => (x) => fns.reduce((v, fn) => fn(v), x);
example
const pipe = (...fns) => (x) => fns.reduce((v, fn) => fn(v), x);

const sanitize = pipe(
  (str) => str.trim(),
  (str) => str.toLowerCase(),
  (str) => str.replace(/[^a-z0-9\s-]/g, ""),
  (str) => str.replace(/\s+/g, "-")
);

console.log(sanitize("  Hello World! @2026  "));
// "hello-world-2026"

Note pipe() applies functions left-to-right; compose() applies right-to-left. Pipe is more intuitive for data transformation chains.

Retry with Exponential Backoff

syntax
async function retry(fn, attempts, delay) { ... }
example
async function retry(fn, maxAttempts = 3, baseDelay = 1000) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxAttempts) throw err;
      const delay = baseDelay * 2 ** (attempt - 1);
      console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

// Usage
const data = await retry(() => fetch("/api/flaky-endpoint").then(r => r.json()));

Note Exponential backoff (1s, 2s, 4s...) prevents overwhelming failing services. Add jitter (random offset) in production to avoid thundering herd.