Facebook Pixel

Implementing once, memoize, and debounce With Closures in JavaScript

Three essential utility functions that use closures. Here is how to implement each from scratch.

Implementing once, memoize, and debounce With Closures in JavaScript

once, memoize, and debounce are three essential utility functions. All three rely on closures. Here is how to implement each.

once: Run a Function Only Once

function once(fn) { let done = false; let result; return (...args) => { if (done) return result; done = true; result = fn(...args); return result; }; } const init = once(() => { console.log("initializing"); return 42; }); init(); // logs "initializing", returns 42 init(); // returns 42 (no log) init(); // returns 42 (no log) `` **How it works**: `done` and `result` are closed over. The first call sets `done = true` and stores the result. Subsequent calls return the stored result without calling `fn`. **Use case**: initialization that should only happen once (database connection, config loading). ### `memoize`: Cache Function Results ```js function memoize(fn) { const cache = {}; return (...args) => { const key = JSON.stringify(args); if (!(key in cache)) { cache[key] = fn(...args); } return cache[key]; }; } const fib = memoize((n) => (n < 2 ? n : fib(n - 1) + fib(n - 2))); fib(40); // fast (results are cached) fib(40); // instant (cached) `` **How it works**: `cache` is closed over. Each call serializes the arguments as a key. If the key is in the cache, return the cached result. Otherwise, call `fn` and store the result. **Use case**: expensive pure functions (fibonacci, factorial, data transformations). **Note**: `JSON.stringify` is a simple key strategy. For object arguments, you may need a custom serializer. ### `debounce`: Delay Until Activity Stops ```js function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } const search = debounce((q) => { console.log("searching for:", q); fetchResults(q); }, 300); input.addEventListener("input", (e) => search(e.target.value)); // "searching for: hello" only fires after the user stops typing for 300ms `` **How it works**: `timer` is closed over. Each call clears the previous timer and sets a new one. The function only runs after the user stops calling for `delay` ms. **Use case**: search inputs, window resize handlers, auto-save. ### Bonus: `throttle`: Limit to Once Per Interval ```js function throttle(fn, delay) { let last = 0; return (...args) => { const now = Date.now(); if (now - last >= delay) { last = now; fn(...args); } }; } const onScroll = throttle(() => { console.log("scroll handler ran"); }, 100); window.addEventListener("scroll", onScroll); // runs at most once per 100ms `` **How it works**: `last` is closed over. Each call checks if enough time has passed. If yes, run `fn` and update `last`. If no, skip. **Use case**: scroll, resize, mousemove events that fire rapidly. ### The Takeaway `once` uses a `done` flag (closed over) to run a function only once. `memoize` uses a `cache` object (closed over) to cache results. `debounce` uses a `timer` (closed over) to delay until activity stops. `throttle` uses a `last` timestamp (closed over) to limit to once per interval. All four rely on closures to persist state across calls.

Close over a done flag and a result variable. The first call sets done to true, calls the original function, and stores the result. Subsequent calls return the stored result without calling the function again.

Close over a cache object. Serialize the arguments as a key. If the key is in the cache, return the cached result. Otherwise, call the original function, store the result in the cache, and return it. The cache is private to the closure.

Close over a timer variable. Each call clears the previous timer and sets a new one with setTimeout. The function only runs after the user stops calling for the specified delay. The timer persists across calls via the closure.

Debounce delays the call until activity stops for the specified delay (search input). Throttle limits the call to at most once per interval (scroll handler). Both use closures: debounce closes over a timer, throttle closes over a last timestamp.

once: initialization that should only happen once. memoize: caching expensive pure functions. debounce: search inputs, auto-save, window resize. throttle: scroll, mousemove, and other rapidly-firing events.

Ready to master React completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.