Live search over a keystroke stream
Requirement
A search box emits every keystroke as a Dart Stream — a
user typing toward darts, with some values repeated by key
autorepeat (fixed sequence in the code below). Turn that into backend
searches: skip queries shorter than two characters, never search the
same query twice, stop after four searched queries, and print each
query with its hit count and top hit — plus how many backend calls were
actually made.
With fxStream the keystroke stream becomes a pipeline, and
each rule becomes an operator: filter for the length
floor, uniq for the repeats, take(4) for the
budget, then map performs the search. Because
take sits before the search step and the chain is
pull-based, exactly four backend calls happen and the tail of the
stream is never consumed.
Expected output
live search over the keystroke stream: 'da' -> 5 hits (top: dart language tour) 'dar' -> 5 hits (top: dart language tour) 'dart' -> 5 hits (top: dart language tour) 'darts' -> 1 hit (top: darts scoring rules) backend searches: 4
Side by side
Native Dart
FxDart
Why they differ
The native await for loop is compact — but look at where
the rules went: the length floor and the dedupe share one
continue expression (q.length < 2 ||
!seen.add(q), which smuggles a mutation into a condition), and
the budget is a counter check with a break. Three policies
compressed into two guard clauses; adding a fourth means untangling
them. The pipeline spends one named operator per rule, in the order
they apply, and the same chain would accept a real widget's text-change
stream unmodified. One honest caveat: fxdart's debounce is
a function-call utility, not a stream operator — quieting a chatty
stream by time is a different tool than the four rules shown here.
Benchmark
Async case: the headline scale is N = 100,000, not 1,000,000. Every element costs an event-loop turn on both sides, so a million real awaits would measure Dart's event loop for minutes — not the pipeline. Delays are zero-length and the example's concurrency limit is kept; what the bars compare is the pipeline machinery.
N = 100
Time Tie
Peak memory Tie
N = 10,000
Time Native wins
Peak memory Tie
N = 100,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.