Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

WeakMap: Attach Data to DOM Elements Without Leaking Memory

C
Carlo Fontanos
· 2 min read

Here's a leak I've written myself: a Map from DOM elements to their widget state. Rows get removed by AJAX refreshes, but the Map still holds references to every dead node - so the garbage collector can't touch them, and memory climbs all afternoon. The fix isn't cleanup discipline; it's the right data structure.

const state = new WeakMap();

function initTooltip(el, options) {
    state.set(el, { options, visible: false, timer: null });
}

function onHover(el) {
    const s = state.get(el);   // element's private state, no DOM inspection
    s.timer = setTimeout(() => show(el, s.options), 300);
}

WeakMap keys are held weakly: when the element is removed from the DOM and nothing else references it, the garbage collector reclaims both the node and its WeakMap entry. No teardown code, no leak, by construction. This is essentially how jQuery's .data() works internally, and how frameworks associate component instances with nodes.

Why not data- attributes?

dataset stores strings on the element itself - great for config and CSS bridging, useless for live objects, timers, functions or anything with identity. WeakMap stores real values, invisible to the DOM, un-serializable by design. They're complements, not competitors: declarative config in data-, runtime state in WeakMap.

Pattern two: memoize by object

const measured = new WeakMap();

function dimensions(imageObject) {
    if (!measured.has(imageObject)) {
        measured.set(imageObject, expensiveMeasure(imageObject));
    }
    return measured.get(imageObject);
}

A cache keyed by object that self-evicts when the objects die. A normal Map here pins every object ever measured; the WeakMap version's memory tracks live objects only. (PHP has the identical structure for the identical reason - WeakMap arrived in PHP 8.0.)

The restrictions are the feature

  • Keys must be objects (or non-registered symbols) - primitives can't be weakly held.
  • No .size, no iteration, no .clear(). You can't enumerate a WeakMap, because between checks the GC may have removed entries - the API refuses to expose non-determinism.

If you need to iterate, you wanted a Map and manual lifecycle management. If entries should mirror the lifetime of their keys, WeakMap is correct - and the lack of iteration also makes it the classic home for genuinely private per-instance data (a module-level WeakMap keyed by this, unreadable from outside, though native #private fields cover most of that ground now).

WeakSet is the same idea minus values - "have I processed this node already?" - one .add() and .has() per element, zero leak potential. Between the two, most "track things about objects I don't own" problems have a clean answer.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Message sent!

Your details are only used to reply to you.

Keep reading