
Functional programming for Dart,
with laziness and concurrency built in.
FxDart is a Dart port of FxTS — a library for composing lazy pipelines over sync and async data, where turning six sequential 1-second requests into a 2-second concurrent batch is one method call.
📒 See it in action: Daily Ledger — a full app built with FxDart →
⚖️ Dart vs FxDart — 53 real tasks, side by side →
⚡ RxDart vs FxDart — push vs pull, 50 honest verdicts →
▲ This is live — edit the code and press Run. It compiles with the real Dart compiler and executes in your browser.
What is FxDart?
FxDart brings the FxTS programming model to Dart: a toolkit of ~120 small, composable functions for transforming collections and asynchronous data. Three ideas define it:
- Lazy evaluation — operators like
map,filter, andtakebuild a pipeline but do no work until a terminal operator (toList,each,reduce…) pulls values through it. Processing a million-element range with.take(3)computes exactly 3 results. - One model for sync and async — the same operator names
work on plain
Iterables and onFxAsyncIterable, FxDart's pull-based async sequence (withStreambridges both ways). - Declarative concurrency —
concurrent(n)asks the upstream pipeline to evaluatenitems at a time while keeping results in order. It is FxTS's signature feature, ported faithfully to Dart.
Events over time, and failures as values, live on the other two surfaces. Which surface? is the decision; this page stays the marketing one.
Why do we need it?
Dart already has Iterable and Stream. FxDart earns
its place where those run out:
| Problem | Plain Dart | FxDart |
|---|---|---|
| Limit concurrent API calls to n, keep order | Manual queues, Completers, careful bookkeeping |
.map(fetch).concurrent(3) |
| Async transform pipelines | Stream is push-based; mixing await, backpressure and laziness gets intricate |
Pull-based chain — each value is computed only when asked for |
| Data wrangling (group, index, count, partition, zip, chunk…) | Hand-rolled loops each time | One well-tested named function per concept |
| Readable multi-step transforms | Nested calls or intermediate variables | Left-to-right fx() chains, fully typed |
Pros & Cons
✓ Pros
- Laziness for free — pipelines short-circuit; only requested values are computed.
- Order-preserving concurrency with a single operator:
concurrent(n)/ completion-orderconcurrentPool(n). - Fully typed chains —
fx()keeps inference end-to-end; sync operators are plain functions over nativeIterables, so everything interops with ordinary Dart. - Small, focused functions — ~120 operators covering transform / filter / slice / combine / aggregate / object / util.
- Battle-tested semantics — behavior ported from FxTS along with 850+ of its tests.
- Zero dependencies — pure Dart.
✗ Cons
- No data-last currying — Dart lacks function overloads, so FxTS's curried
pipestyle becomes chains; the dynamicpipe()loses static types. - A second async abstraction —
FxAsyncIterableexists becauseStreamcan't express the concurrency back-channel; bridging is easy but it is one more concept to learn. - Learning curve — thinking in lazy pipelines differs from imperative loops.
- Not always fastest — for tiny hot loops, a hand-written
forcan beat operator composition; FxDart optimizes for clarity and I/O-bound work. - Some TS APIs don't port literally — they get Dart-native spellings instead:
currybecomes the typed.curriedextension getter, with the old names kept as deprecated stubs for migration.
Taste of concurrency
Six fake requests of 300 ms each — sequentially ~1.8 s, with
concurrent(3) about 0.6 s. Try changing the number: