#10292·tldraw

Timers never prunes fired or cleared timer ids, so editor.timers grows without bound

Author: steveruizokCreated Aug 20, 2026Updated Sep 15, 2026
Labelsperformancesdk

Timers.setTimeout / setInterval / requestAnimationFrame in packages/utils/src/lib/timers.ts append every id to a per-context array that is only pruned by dispose(contextId), never when a timer fires or is cleared, and each call copies the whole array:

const id = window.setTimeout(handler, timeout, args)
const current = this.timeouts.get(contextId) ?? []
this.timeouts.set(contextId, [...current, id])
return id

Every pointer_down creates a long-press timeout and a ClickManager click timeout (and perf listeners add a timeout per camera event). After N calls the array holds N stale ids for the editor's lifetime and each new call is O(N), so a long session degrades quadratically and dispose() calls clearTimeout N times. (ScribbleManager's per-frame idle timer, the worst producer, was fixed in https://github.com/tldraw/tldraw/pull/10282; the underlying growth remains.)

Suggested fix: track ids in a Set per context, wrap the handler to delete its own id when it fires, and delete on clearTimeout / clearInterval / cancelAnimationFrame.