Skip to content

refactor: use Set instead of Array for O(1) listener operations in singleton-handler#187

Merged
NathanZlion merged 1 commit intocloudscape-design:mainfrom
TrevorBurnham:optimize-singleton-handler-set
Jan 28, 2026
Merged

refactor: use Set instead of Array for O(1) listener operations in singleton-handler#187
NathanZlion merged 1 commit intocloudscape-design:mainfrom
TrevorBurnham:optimize-singleton-handler-set

Conversation

@TrevorBurnham
Copy link
Contributor

This PR replaces the Array with a Set in createSingletonHandler for managing listeners, improving the time complexity of listener removal from O(n) to O(1).

Motivation

The createSingletonHandler is used by useMutationSingleton, which powers useCurrentMode, useDensityMode, and useReducedMotion. These hooks observe document.body with { attributes: true, subtree: true }, meaning the MutationObserver fires on every attribute change anywhere in the DOM.

In applications with many Cloudscape components using these hooks, the listener collection can grow large. The previous implementation used:

listeners.splice(listeners.indexOf(listener), 1);

This is O(n) for each component unmount. With the Set-based approach:

listeners.delete(listener);

Removal is O(1).

Changes

  • src/internal/singleton-handler/index.ts: Replace Array with Set for listener storage
  • src/internal/singleton-handler/__tests__/create-singleton-handler.test.tsx: Add tests for listener order preservation and rapid mount/unmount cycles

Complexity comparison

Operation Array (before) Set (after)
Add listener O(1) push() O(1) add()
Remove listener O(n) indexOf() + splice() O(1) delete()
Check empty O(1) length O(1) size
Iterate O(n) O(n)

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

…ngleton-handler

Replace Array with Set in createSingletonHandler for managing listeners.
This improves performance for component mount/unmount operations:

- listeners.add() is O(1) vs Array.push() O(1) - same
- listeners.delete() is O(1) vs Array.indexOf() + splice() O(n)
- listeners.size is O(1) vs Array.length O(1) - same

This is significant because useMutationSingleton (used by useCurrentMode,
useDensityMode, useReducedMotion) watches document.body with
{ attributes: true, subtree: true }, firing on every DOM attribute change.
Component unmounts in apps with many listeners benefit from O(1) removal.

Sets maintain insertion order (ES2015+), preserving iteration consistency.

Added tests for:
- Listener notification order preservation
- Rapid mount/unmount cycle handling
@TrevorBurnham TrevorBurnham requested a review from a team as a code owner January 26, 2026 16:40
@TrevorBurnham TrevorBurnham requested review from NathanZlion and removed request for a team January 26, 2026 16:40
@codecov
Copy link

codecov bot commented Jan 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.00%. Comparing base (c09d82c) to head (d613b87).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #187   +/-   ##
=======================================
  Coverage   99.00%   99.00%           
=======================================
  Files          42       42           
  Lines        1101     1101           
  Branches      280      296   +16     
=======================================
  Hits         1090     1090           
+ Misses         11       10    -1     
- Partials        0        1    +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@NathanZlion NathanZlion added this pull request to the merge queue Jan 28, 2026
Merged via the queue into cloudscape-design:main with commit 012284d Jan 28, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants