Skip to content

Memory hygiene guide

Every timer, and where it stops

An animation library with a leak is worse than a slow one: a page that keeps a timer alive keeps the CPU awake and the battery draining long after the tab is hidden. This audit counts registrations against cleanups across the whole source tree, which is a code reading rather than a profiler trace — the honest limits of which are stated below the table.

Intervals / timeouts

110 set · 76 cleared

setInterval + setTimeout against clearInterval + clearTimeout

Animation frames

70 requested · 52 cancelled

a one-shot frame never needs cancelling

Listeners

57 added · 51 removed

element and window listeners

Registrations against cleanups, per file

A file where registrations outnumber cleanups is not automatically wrong — a one-shot requestAnimationFrame and a listener on an element that is going away both need no cleanup. It is a list to read, not a verdict, and the files with the biggest gaps are at the top.

Data table — data table
FilesetInterval/Timeoutclear…rAFcancelAFaddListenerremoveListener
asset-detail.tsx15113121
demos/scenes/set-04.tsx1250011
demos/scenes-17.tsx229322
community-ui-2.tsx005577
community-ui.tsx116644
admin-ui-2.tsx004466
retention-ui-2.tsx003377
demos/scenes/set-01.tsx235411
demos/scenes/set-02.tsx760000
demos/scenes/set-11.tsx680011
demos/scenes/set-05.tsx432111
lab-tools.tsx205300

The house rules the code follows, and the ones this table is read against: every setInterval is cleared in the effect cleanup; every addEventListener is paired with a remove in the same effect; a loop that needs to run while visible is driven by one frame at a time rather than by a timer at a fixed rate.

29 of 63 files register more timers or frames than they clear. The largest gap is asset-detail.tsx — worth a read rather than a rewrite, since a one-shot frame accounts for most gaps of one.

Loops in scenes

74

20 interval · 36 timeout · 18 rAF

Intervals cleared

20/20

setInterval against clearInterval

Sub-100 ms intervals

6

tight loops, flagged below

Uncleaned intervals

0

a loop with no clear in its scene

Every loop in the scene modules, by scene

Extracted from source at build time: the scene, the kind of loop, its interval and whether the same function cleans it up. This is a registry, not a verdict — a one-shot rAF and a timeout on a message that goes away both legitimately need no cleanup — but a setInterval with no clear, or one firing faster than 100 ms, is visible here before a browser has to prove it leaks.

Data table — data table
SceneKindIntervalCleanupWhere
ReorderListraf() => { el.style.transition = reduced ? "none" : "transform 220ms cubic-bezier(0.22, 1, 0.36, 1)"; el.style.transform = ""; }one-shotscenes-17.tsx:87
DrawOnScrollraf() => setDrawn(true)one-shotscenes-17.tsx:1113
MorphIconsraftickclearedscenes-17.tsx:1222
MorphIconsraftickclearedscenes-17.tsx:1224
AuditRingrafstepclearedscenes-17.tsx:1520
AuditRingrafstepclearedscenes-17.tsx:1532
AuditRingrafstepclearedscenes-17.tsx:1534
OdometerValuerafstepclearedscenes-17.tsx:1900
OdometerValuerafstepclearedscenes-17.tsx:1902
PulseGraphinterval900clearedscenes-17.tsx:2766
PreloaderHandoffintervalSTAGE_MSclearedscenes-17.tsx:3040
HaloTrailraf() => { setHalos((prev) => prev.filter((h) => h.id > id - n)); }clearedscenes/set-01.tsx:204
ScrambleTextraftickclearedscenes/set-01.tsx:400
ScrambleTextraftickclearedscenes/set-01.tsx:402
CounterStatsraftickclearedscenes/set-01.tsx:906
CounterStatsraftickclearedscenes/set-01.tsx:908
DotDrawtimeout900clearedscenes/set-01.tsx:962
TextCycleinterval2600clearedscenes/set-01.tsx:1030
CommandPalettetimeout1400unclearedscenes/set-02.tsx:191
ToastStacktimeoutttlunclearedscenes/set-02.tsx:285
TestimonialRotatorintervalspclearedscenes/set-02.tsx:630
CountdownDropinterval1000clearedscenes/set-02.tsx:693
TerminalHerotimeoutspclearedscenes/set-02.tsx:759
TerminalHerotimeout4200clearedscenes/set-02.tsx:764
OdometerCounterinterval42clearedscenes/set-02.tsx:1027
QuantityStepperinterval110clearedscenes/set-03.tsx:188
FileDropZoneinterval90clearedscenes/set-03.tsx:426
FileDropZonetimeout250unclearedscenes/set-03.tsx:431
SplitButtonMenutimeout700unclearedscenes/set-03.tsx:640
SkeletonCardInnertimeoutdelayclearedscenes/set-04.tsx:176
StatusBannertimeout1600unclearedscenes/set-04.tsx:264
ProgressRinginterval130clearedscenes/set-04.tsx:285
ProgressRingtimeout200unclearedscenes/set-04.tsx:289
SpinnerStatustimeout1400unclearedscenes/set-04.tsx:385
SpinnerStatustimeout3200unclearedscenes/set-04.tsx:386
OfflineIndicatortimeout2800unclearedscenes/set-04.tsx:490
OfflineIndicatortimeout3400unclearedscenes/set-04.tsx:491
ErrorBoundaryCardtimeout1400unclearedscenes/set-04.tsx:541
DotLeaderLoadingtimeout2500clearedscenes/set-04.tsx:647
StaggeredListEntrancetimeout60unclearedscenes/set-04.tsx:1034
StaggeredListEntrancetimeout620unclearedscenes/set-04.tsx:1035
ShuffleKenburnsGallerytimeout6200clearedscenes/set-05.tsx:53
ParticleTrailHerotimeout850clearedscenes/set-05.tsx:180
RippleRevealtimeout800unclearedscenes/set-05.tsx:395
WordHighlighttimeoutmsclearedscenes/set-05.tsx:583
StatsBandraftickclearedscenes/set-05.tsx:1063
StatsBandraftickclearedscenes/set-05.tsx:1065
MapFreeLocalBandinterval30000clearedscenes/set-06.tsx:636
AppScreenshotTourtimeout3400clearedscenes/set-06.tsx:713
TemplateWaitlistinterval1000clearedscenes/set-06.tsx:994
TemplateWaitlisttimeout1400unclearedscenes/set-06.tsx:1009
TemplateGallerytimeout1200unclearedscenes/set-07.tsx:26
WaterRippletimeout2200clearedscenes/set-07.tsx:652
RippleButtontimeout600unclearedscenes/set-08.tsx:29
NumberTickerScrollrafstepone-shotscenes/set-08.tsx:69
NumberTickerScrollrafstepone-shotscenes/set-08.tsx:71
LongPressButtoninterval40clearedscenes/set-08.tsx:94
CopyIconButtontimeout1400unclearedscenes/set-08.tsx:155
ScrambleOnViewinterval50clearedscenes/set-11.tsx:30
TypewriterCursorinterval60clearedscenes/set-11.tsx:72
TabAwareCounterinterval40clearedscenes/set-11.tsx:93
FlipClockinterval5000clearedscenes/set-11.tsx:264
PointerImageTrailtimeout800unclearedscenes/set-11.tsx:343
LetterRisetimeout200clearedscenes/set-11.tsx:430
PullToRefreshtimeout900unclearedscenes/set-12.tsx:177
ToastTimerBartimeout4000unclearedscenes/set-12.tsx:399
VerticalCarouselinterval4000clearedscenes/set-12.tsx:425
HeadlineSwapinterval2600clearedscenes/set-12.tsx:456
TimeZonePickertimeout0clearedscenes/set-14.tsx:39
TimeZonePickerinterval30000clearedscenes/set-14.tsx:42
CronEditortimeout0clearedscenes/set-14.tsx:157
CronEditorinterval30000clearedscenes/set-14.tsx:161
ChatComposertimeout0clearedscenes/set-14.tsx:366
ChatComposertimeout1500clearedscenes/set-14.tsx:388

What this audit does not do

It counts call sites, not live handles. A file with equal registrations and cleanups can still leak if the cleanup is in the wrong place, and a file with a gap of one is usually a single-shot frame that never needs cancelling. The measurement that settles it is a heap snapshot after navigating between twenty pages, and that needs a browser attached — which is on the device matrix page as a method, not as a result.

Per-frame work

#406 — a rAF loop is the only thing here that runs per frame, and the registry counts them: every requestAnimationFrame in the scene modules is a named row with its cleanup stated. The interval table above is the tick side; this is the frame side. The heaviest consumers are named rather than averaged.