Skip to content

Conversation

@jeremiahseun
Copy link

@jeremiahseun jeremiahseun commented Jan 27, 2026

Replaced List with Set in _collectMaskWidgetRects to improve performance from O(N^2) to O(N) by avoiding linear scan for contains checks. Included a benchmark test demonstrating ~17x speedup for a tree with ~5500 elements.

💡 Motivation and Context

The _collectMaskWidgetRects method previously used a List to track collected rectangles. It performed a !rectList.contains(element.rect) check for every element during the recursive traversal.

Since List.contains is an O(N) operation, and this check runs for every element in the tree, the overall complexity became O(N^2). For large widget trees (e.g., complex screens with thousands of elements), this caused significant performance degradation during session replay capture.

This change replaces the List with a Set for the accumulation phase. Set.contains and Set.add are O(1) operations, effectively bringing the total complexity down to O(N). The result is converted back to a List at the end to preserve the API signature.

🤔 Why did I do this / How did I discover this?

I was profiling our app's performance because we noticed intermittent stuttering (dropped frames) on our main 'Feed' screen, but only when Session Replay was enabled.

This screen is quite complex—it’s a long ListView with nested widgets, easily containing thousands of elements. I popped open the Flutter DevTools CPU Profiler to see what was eating up the frame time.

I expected the layout or painting to be the bottleneck, but I was surprised to see a significant chunk of time spent in ElementData.extractMaskWidgetRects. digging into the source code, I spotted the culprit immediately: a List.contains() check running inside a recursive loop.

In Computer Science terms, we were accidentally doing an O(N²) operation on every snapshot. For a small screen, you barely notice it. But on our feed with ~5,000 nodes, it was taking over 100ms—completely blocking the UI thread and causing visible lag.

💚 How did you test it?

I added a new benchmark test test/reproduction_benchmark.dart that generates a deep widget tree with ~5,500 ElementData nodes.

Benchmark Results (Avg per iteration):

Before: ~132 ms
After: ~7.6 ms
Improvement: ~17x faster
I also ran the full existing test suite (flutter test) to ensure no regressions in functionality.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changeling

Screenshots

Screenshot 2026-01-27 at 19 00 46

Replaced List with Set in _collectMaskWidgetRects to improve performance from O(N^2) to O(N) by avoiding linear scan for contains checks.
Included a benchmark test demonstrating ~17x speedup for a tree with ~5500 elements.

Co-authored-by: jeremiahseun <53568423+jeremiahseun@users.noreply.github.com>
@jeremiahseun jeremiahseun requested a review from a team as a code owner January 27, 2026 18:24
@marandaneto
Copy link
Member

@jeremiahseun makes sense, thanks
left a comment, otherwise mind running

formatDart:
and then adding an item to the changelog? https://github.com/PostHog/posthog-flutter/blob/main/CHANGELOG.md#next (under next)

@jeremiahseun
Copy link
Author

Hi @marandaneto, I have removed the test file and run the format command.

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.

2 participants