Spend grouped by category

FxDart wins

Requirement

Total nine August transactions per category, and print the totals in the order each category first appears in the statement. The data is in the code; both versions must print the lines shown under Expected output.

Expected output
groceries: 69
transport: 23
dining: 72

Side by side

RxDart

FxDart

Why they differ

Grouping is where the push model's commitment to "everything is a stream" gets expensive. RxDart's groupBy cannot return a map — the source may never end — so it returns a stream of streams: one GroupedStream per new key. To get totals out, each inner stream must be folded (a Future), the future lifted back into a stream (asStream), and the results merged with flatMap — three layers of plumbing around one sum. (A pragmatic rx user can dodge groupBy entirely by folding the whole stream into a mutable map — shorter, but it abandons the operator this example is about and the grouping becomes imperative again.) And the shape has sharp edges: fold with asyncExpand instead of flatMap and the program deadlocks, because pausing the outer stream while waiting for a group total stops the source that must complete before any group can close.

FxDart's data is finite by construction, so grouping needs no streams-of-streams: groupedBy yields plain (key, items) records in first-seen key order and the chain keeps going, with sumBy doing the arithmetic per group. Nothing is deferred because nothing is still arriving. For live, unbounded feeds the GroupedStream design is the right call — but for a statement that is already at hand, this is a pull-shaped job and the pull version says so in three lines. The verdict goes to FxDart.

Benchmark

Apple M1 Max, 32 GB RAM · Dart 3.12.2 (AOT-compiled) · 2026-08-18

N = 100

Time Tie

RxDart 24 µs
FxDart 4.9 µs

Peak memory Tie

RxDart 16.7 MB
FxDart 16.4 MB

N = 1,000,000

Time FxDart wins

RxDart 104.8 ms
FxDart 44.6 ms

Peak memory Tie

RxDart 92.5 MB
FxDart 96.2 MB

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.