FrontendAtlas
Interview Questions
Home>JavaScript interview questions> JavaScript coding challenges >Cleanup Bag (Dispose Subscriptions)

Cleanup Bag (Dispose Subscriptions)

easy
By FrontendAtlas Editorial · Updated Feb 3, 2026
Implement createCleanupBag() that helps prevent memory leaks by tracking cleanup functions (like removing event listeners or cancelling timers). It should return { add, dispose, size }. add(fn) registers a cleanup and returns a function to remove it. dispose() runs all remaining cleanups once and clears the bag.

Returns

{ add: (fn: () => void) => () => void, dispose: () => void, size: () => number } — A cleanup registry that can be disposed safely.
Examples
const bag = createCleanupBag();
const off = bag.add(() => clearInterval(id));
// later...
bag.dispose();

// add after dispose runs immediately
bag.dispose();
bag.add(() => console.log('cleanup'));

Solution

Overview

Track cleanup callbacks in a Set. add() registers a cleanup and returns a remover. dispose() runs each cleanup once and clears the Set. If add() is called after dispose(), run it immediately to avoid leaks.

1

Approach: Set + idempotent dispose

Steps:

1) Keep a Set of cleanup functions.

2) add(fn): if already disposed, run fn immediately and return a no-op remover; otherwise store fn and return a remover that deletes it.

3) dispose(): run all remaining cleanups once, clear the Set, mark disposed.

Complexity: O(n) to dispose; O(1) per add/remove.

export default function createCleanupBag() {
  const cleanups = new Set();
  let disposed = false;

  const add = (fn) => {
    if (typeof fn !== 'function') throw new TypeError('cleanup must be a function');
    if (disposed) {
      fn();
      return () => {};
    }
    cleanups.add(fn);
    let removed = false;
    return () => {
      if (removed) return;
      removed = true;
      cleanups.delete(fn);
    };
  };

  const dispose = () => {
    if (disposed) return;
    disposed = true;
    for (const fn of Array.from(cleanups)) {
      try { fn(); } finally { cleanups.delete(fn); }
    }
  };

  const size = () => cleanups.size;

  return { add, dispose, size };
}
export default function createCleanupBag() {
  const cleanups = new Set<() => void>();
  let disposed = false;

  const add = (fn: () => void) => {
    if (typeof fn !== 'function') throw new TypeError('cleanup must be a function');
    if (disposed) {
      fn();
      return () => {};
    }
    cleanups.add(fn);
    let removed = false;
    return () => {
      if (removed) return;
      removed = true;
      cleanups.delete(fn);
    };
  };

  const dispose = () => {
    if (disposed) return;
    disposed = true;
    for (const fn of Array.from(cleanups)) {
      try { fn(); } finally { cleanups.delete(fn); }
    }
  };

  const size = () => cleanups.size;

  return { add, dispose, size };
}

Notes & Pitfalls

Pitfalls
  • Forgetting to clear references keeps memory alive unnecessarily.
  • Calling cleanup twice can cause errors; make dispose idempotent.
  • Not handling add-after-dispose can leak resources.
Edge cases
  • dispose() called multiple times should be safe.
  • remove a cleanup before dispose should skip it.
  • add() after dispose should run immediately.
Techniques
  • Set for stable O(1) add/remove.
  • Idempotent cleanup patterns.

Common mistakes on this challenge

  • Cleanup bag lifecycle behavior is inconsistent

    This utility should be idempotent and leak-safe across add/remove/dispose flows.

    • Store cleanups in a `Set` and return a remover that deletes only once.
    • Make `dispose()` idempotent and run each remaining cleanup exactly once.
    • If `add()` is called after dispose, run the cleanup immediately and keep bag size at 0.

Resources

  • MDN – addEventListener/removeEventListener

Similar questions

Run With a Performance Budget (Sync or Async)intermediateCreate a Spy Function (Test Double)intermediateCreate an LRU Cache (Bounded Memory)hard

Guides

Frontend interview preparation guideGuideFrontend coding interview questions and prep guideBlueprintJavaScript Problems That Actually Show UpBlueprintBuild Great UI in 60 MinutesBlueprint

Preparing for interviews? Use Frontend Coding Challenges first, then move into a concrete Study Plan before targeted Company Prep.

Open frontend interview questionsBrowse JavaScript interview questionsOpen Essential 60Open Machine Coding HubOpen Frontend Coding ChallengesOpen System DesignOpen Interview Prep GuideOpen System Design BlueprintOpen Framework Prep PathsOpen Study PlansOpen JavaScript mastery study planOpen Company PrepOpen JavaScript Framework Prep Guide
↗Incidents hub
← Prev←41 / 88