Monthly category report, sorted by spend
Requirement
From a ledger that spills over from June into July 2026, build the July spending report: keep only July transactions, total each category, and print one line per category — biggest spend first. The data is in the code below; both versions must print the lines shown under Expected output.
Expected output
Food: $74.60 Bills: $60.34 Fun: $23.25 Transport: $14.15
Side by side
Native Dart
FxDart
Why they differ
Native Dart has no groupBy, so the loop does the grouping
and the totalling at once inside a mutable map — compact, but the four
requirements (July only, per category, totalled, ranked) are tangled
into one body. The FxDart chain keeps them as four visible steps:
filter the month, groupBy category,
map each group to its total, sortBy descending
— and join formats the report. Adding a requirement
(say, a minimum total) is one more chain step; in the loop it is
another branch inside an already-busy body.
Two FxDart spellings
The benchmark below carries a third bar, which only one
other page does. The chain above is the one to write: filter
and foldBy as two named steps, each answering one question.
What it cannot do is inline its own predicate. filter is a
lazy stage, so it keeps that predicate in an iterator field, and the AOT
compiler cannot see through a field — every transaction pays a real
indirect call whose body never fuses into the loop. foldBy
does not have that problem; it is strict, so its callbacks are parameters
and get inlined.
foldByOrSkip,
shown above main in the FxDart panel, moves the test into the
key: a null key skips the row, so one callback both selects
and buckets, and it is a parameter of a body small enough to inline. Over
1,000,000 transactions that is the difference between the second and third
bars; the first is the hand-written loop.
Write the chain by default — two unrelated questions read better as two
steps. Reach for foldByOrSkip when the pipeline is hot and a
profile says that predicate is the cost.
Benchmark
N = 100
Time Tie
Peak memory Tie
N = 10,000
Time Tie
Peak memory FxDart wins
N = 1,000,000
Time Native wins
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.