Cohort retention table
Requirement
Each user (data in the code) has a signup month and the list of months they were active. Group users into cohorts by signup month and, for every later month, print what percentage of the cohort was still active — one row per cohort, oldest first. Both versions must print the table shown under Expected output.
Expected output
Cohort retention by signup month 2026-04 (4 users): 2026-05 50% | 2026-06 50% | 2026-07 25% 2026-05 (3 users): 2026-06 33% | 2026-07 67% 2026-06 (2 users): 2026-07 50%
Side by side
Native Dart
FxDart
Why they differ
A retention table is a pipeline inside a pipeline: cohorts on the
outside, months on the inside. In FxDart both layers are expressions —
groupBy → sortBy → map over
cohorts, and inside each row dropWhile skips months up to
the signup month before filter + size count the
still-active users. The native version needs a mutable accumulator list
per layer (rows, cells) and two nested
for loops to stitch them together; the actual retention
logic is the same, but it is spread across loop bodies instead of being
the visible spine of the code.
Benchmark
N = 100
Time Tie
Peak memory Tie
N = 10,000
Time Tie
Peak memory Tie
N = 1,000,000
Time Tie
Peak memory Tie
Bars are medians of repeated timed iterations in fresh processes per side (small N is batched for timer resolution). Sides within 5% of each other — or within 0.6 ms, a difference no person can perceive — count as a tie; close relative races are re-measured up to 5 runs. In an app, anything under a few milliseconds is invisible to the user regardless of which bar is shorter. Memory is peak process RSS. The Dart VM and the dataset are identical on both sides, so the difference between the two bars is what the pipeline itself holds onto.