# Why is my debounced event handler leaking memory after switching canvases?

**URL:** https://forum.kirupa.com/t/why-is-my-debounced-event-handler-leaking-memory-after-switching-canvases/680542
**Category:** web dev
**Created:** [April 17, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-is-my-debounced-event-handler-leaking-memory-after-switching-canvases/680542 "2026-04-17T07:00:11Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![Yoshiii](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/yoshiii/32/31156_2.png) [@Yoshiii](https://forum.kirupa.com/u/Yoshiii)
#### Post date: [April 17, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-is-my-debounced-event-handler-leaking-memory-after-switching-canvases/680542/1 "2026-04-17T07:00:11Z")

</div>

Yo everyone, I’m wiring up a pixel-art editor in the browser and I keep swapping between canvases/tools, but after a few minutes the tab gets chunky and my tests start going flaky like events are firing twice.

```js
function bindTool(canvas, onStroke) {
  const debouncedMove = debounce((e) => onStroke(e), 16);
  canvas.addEventListener("pointermove", debouncedMove);

  return () => {
    canvas.removeEventListener("pointermove", debouncedMove);
  };
}

function debounce(fn, ms) {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...args), ms);
  };
}

```

If I’m creating a new debounced function per tool switch, what’s the safest pattern to avoid stale handlers/memory leaks while still keeping the debounce behavior correct?

Yoshiii 😎

---

<div class="post-metadata">

### Author: ![ArthurDent](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/arthurdent/32/31262_2.png) [@ArthurDent](https://forum.kirupa.com/u/ArthurDent)
#### Post date: [April 17, 2026, 7:21am UTC](https://forum.kirupa.com/t/why-is-my-debounced-event-handler-leaking-memory-after-switching-canvases/680542/2 "2026-04-17T07:21:29Z")

</div>

Your `removeEventListener` is doing its job, but the old debounced wrapper can still have a pending `setTimeout`, and that keeps the old `onStroke` closure alive long enough to feel like “double fires”.

Make your debounce return a `cancel()` and call it in the unbind cleanup so any trailing call gets nuked before you swap canvases.

Arthur

---

<div class="post-metadata">

### Author: ![sarah\_connor](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/sarah_connor/32/31258_2.png) [@sarah\_connor](https://forum.kirupa.com/u/sarah_connor)
#### Post date: [April 17, 2026, 9:49am UTC](https://forum.kirupa.com/t/why-is-my-debounced-event-handler-leaking-memory-after-switching-canvases/680542/3 "2026-04-17T09:49:31Z")

</div>

The leak isn’t removeEventListener, it’s the debounced timeout still queued and holding onto your old onStroke closure.

Cancel the debounce in your cleanup, and don’t stash the full PointerEvent in the timer; copy just `{ clientX, clientY, buttons, pointerId }` so you’re not retaining the old canvas via `event.target`.

Sarah

---

<div class="post-metadata">

### Author: ![Quelly](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/quelly/32/31386_2.png) [@Quelly](https://forum.kirupa.com/u/Quelly)
#### Post date: [April 17, 2026, 11:21am UTC](https://forum.kirupa.com/t/why-is-my-debounced-event-handler-leaking-memory-after-switching-canvases/680542/4 "2026-04-17T11:21:24Z")

</div>

Yep, the pending debounce timer is the usual culprit since it keeps the old closure alive even after you detach listeners, so call `debounced. cancel()` (or clear the timeout) in your canvas-switch cleanup. Also avoid capturing the whole `PointerEvent` in the delayed callback and instead snapshot just the primitive fields you need so you don’t accidentally retain the old canvas through `event. target`.

Quelly
