Recent error messages, deduped

FxDart wins

Requirement

A log store returns entries newest first. Show the three most recent distinct error messages: keep only ERROR entries, drop repeats of a message already shown, and stop after three. The data is in the code below; both versions must print the lines shown under Expected output.

Expected output
09:41 payment gateway timeout
09:31 inventory service 503
09:17 invalid session token

Side by side

Native Dart

FxDart

Why they differ

Dart has no "distinct by key" — deduping by message means managing a Set yourself, so the native version becomes a loop with three concerns braided together: the level check, the seen.add trick, and a counted break. Each is fine alone; together they force you to read the whole loop to see what it keeps. FxDart states the three rules as three chain steps — filter, uniqBy, take — and because the chain is lazy, it also stops scanning the log the moment the third distinct error is found, exactly like the hand-written break.

Two FxDart spellings

The benchmark on this page carries a third bar, which no other comparison does. The chain above is the one to write: three independent rules, read top to bottom, and lazy — it stops scanning at the third distinct error, exactly like the hand-written break. What it cannot do is inline its own callbacks. A lazy stage keeps its closure in an iterator field, and the AOT compiler cannot see through a field, so filter and uniqBy each cost a real indirect call on every element — together, most of what separates this pipeline from the native loop.

takeUniqBy, shown above main in the FxDart panel, is the same pipeline written as one strict call. Its callback is a parameter of a body small enough to inline into the caller, so the compiler inlines the closure with it; one callback does both jobs, with a null key meaning "skip this element". Over 1,000,000 log lines that is the difference between the second and third bars — and the native loop is what the first bar shows.

Write the chain by default. Reach for takeUniqBy when the pipeline is hot and a profile says these callbacks are the cost.

Benchmark

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

N = 100

Time Tie

Native Dart 1.0 µs
FxDart 1.3 µs

Peak memory Tie

Native Dart 16.5 MB
FxDart 16.5 MB

N = 10,000

Time Tie

Native Dart 90 µs
FxDart 127 µs

Peak memory Tie

Native Dart 15.8 MB
FxDart 15.8 MB

N = 1,000,000

Time Native wins

Native Dart 10.3 ms
FxDart 13.7 ms

Peak memory Tie

Native Dart 114.3 MB
FxDart 113.5 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.