Register at a deliberate lifecycle boundary
Avoid attaching a window keydown handler from a render function or every frame. Put scene-specific setup in a start or mount function, and ensure that function cannot run twice without cleanup. Keep a reference to the callback when using removeEventListener; anonymous callbacks created repeatedly are difficult to match later. Alternatively, give a group of related handlers an AbortController and pass its signal in the listener options, then abort that group when the scene ends.
Choose event targets carefully. A global window listener may be appropriate for keys while a game is active, but a button's click handler belongs on that button. Do not register both a parent delegation handler and a direct handler for the same action unless event propagation is intentionally part of the design.
Test repeated scene entry as a symptom check
Open a scene, press its action once, leave, and open it again. One input should still cause one effect. Repeat the cycle five times and verify that the response count remains one per press; this is a functional check, not a memory measurement. Also confirm that input from the old scene no longer changes its score or state after exit. MDN documents AbortSignal support for grouping listeners and notes that anonymous functions need retained references for explicit removal.
Keep teardown aligned with ownership
When a scene is replaced, abort or remove its listeners before disposing of the objects they reference. If some listeners belong to the whole application shell, keep those in a longer-lived owner instead of removing them during every round. Add a regression case around restart, pause, and return-to-menu flows, since each transition can accidentally mount the same scene twice or skip its cleanup.
Questions about prevent duplicate game event listeners
Why does one key press produce multiple game actions?
The same action may have multiple registered handlers, or both an ancestor and a child may process the event. Count registrations and inspect event propagation at the scene lifecycle boundary.
Can AbortController remove event listeners?
Yes. Add related listeners with the same signal and call abort when their owner ends. This gives a group a clear teardown path; it does not replace deciding which owner controls them.