Generators in Dart
last modified October 18, 2026
In this article we explore generators in Dart, the functions that
produce a sequence of values on demand using sync*,
async*, and yield.
Generators Overview
A generator is a function that produces a series of values instead of returning a single one. Instead of building a whole list up front, it hands out values one at a time, lazily, as a consumer asks for them. This is efficient for large or unbounded sequences, because only the values actually used are computed.
Dart has two kinds of generators. A synchronous generator, marked
with sync*, returns an Iterable and uses
yield to emit values. An asynchronous generator,
marked with async*, returns a Stream and may
await between yields. The yield* form delegates to
another generator, forwarding its values.
| Form | Returns | Produces values with |
|---|---|---|
sync* |
Iterable |
yield |
async* |
Stream |
yield (may await) |
yield* |
Delegates to another generator | Forwards Iterable or Stream |
Synchronous Generators
Adding sync* to a function body and using yield turns it
into a generator of an Iterable. The values are produced each time the
iteration advances.
Iterable<int> countTo(int n) sync* {
for (var i = 1; i <= n; i++) {
yield i;
}
}
void main() {
print(countTo(5).toList());
for (final n in countTo(3)) {
print('n=$n');
}
}
countTo looks like a loop, but each yield hands a value
to the caller and pauses there. The first call materialises five values with
toList; the loop then consumes a fresh sequence of three. The
generator can be iterated as many times as it is called, because each call creates
a new iteration.
$ dart run sync_generator.dart [1, 2, 3, 4, 5] n=1 n=2 n=3
Lazy Evaluation
Generators are lazy: no work happens until a value is requested. This makes it possible to define an infinite sequence safely, as long as the consumer only takes a finite number of values.
Iterable<int> naturals() sync* {
var i = 0;
while (true) {
print('generating $i');
yield i++;
}
}
void main() {
print('created');
final firstThree = naturals().take(3).toList();
print(firstThree);
}
naturals loops forever, yet the program terminates. Calling the
generator prints only created; nothing is generated until
take(3) pulls values. Exactly three values are produced, matching the
three generating lines, and the infinite loop is abandoned once the
consumer stops asking.
$ dart run lazy_generator.dart created generating 0 generating 1 generating 2 [0, 1, 2]
Recursive Generators and yield*
The yield* operator delegates to another generator, producing all of
its values in place. This composes generators naturally and makes recursive
traversal concise.
class Node {
final String name;
final List<Node> children;
Node(this.name, [this.children = const []]);
}
Iterable<String> walk(Node node) sync* {
yield node.name;
for (final child in node.children) {
yield* walk(child);
}
}
void main() {
final tree = Node('root', [
Node('a', [Node('a1'), Node('a2')]),
Node('b', [Node('b1')]),
]);
print(walk(tree).toList());
}
walk yields its own name, then delegates to itself for each child with
yield*. The result is a depth-first listing of the whole tree:
root, then the a subtree and its children, then the
b subtree. Without yield* the nested sequences would have
to be flattened by hand.
$ dart run recursive_generator.dart [root, a, a1, a2, b, b1]
Asynchronous Generators
Marking a function async* makes it produce a Stream.
Inside the body you may await between yields, so each
value can depend on asynchronous work such as a network request or a timer.
Stream<int> ticks(int n) async* {
for (var i = 1; i <= n; i++) {
await Future.delayed(const Duration(milliseconds: 20));
yield i;
}
}
Future<void> main() async {
await for (final t in ticks(3)) {
print('tick $t');
}
print('done');
}
Each iteration waits 20 milliseconds and then yields the next tick, so the stream
delivers values over time rather than all at once. The await for loop
in main receives them as they arrive and finishes when the stream is
exhausted.
$ dart run async_generator.dart tick 1 tick 2 tick 3 done
Delegating with yield* in Async Generators
yield* works in async generators too, forwarding every event from
another stream. It is useful for concatenating or composing streams.
Stream<int> numbers() async* {
yield 1;
yield 2;
}
Stream<int> combined() async* {
yield* numbers();
yield 3;
}
Future<void> main() async {
await for (final n in combined()) {
print(n);
}
}
combined first delegates to numbers with
yield*, emitting 1 and 2, and then yields
3 itself. The consumer sees a single stream 1, 2, 3 and
never needs to know it was assembled from two generators.
$ dart run async_delegate.dart 1 2 3
Chunking a Sequence
A practical use of generators is splitting a long collection into fixed-size chunks without building all the chunks up front. The generator allocates and produces one chunk at a time as the consumer requests it.
Iterable<List<int>> chunked(List<int> items, int size) sync* {
if (size <= 0) {
throw ArgumentError.value(size, 'size', 'must be positive');
}
for (var i = 0; i < items.length; i += size) {
final end = i + size < items.length ? i + size : items.length;
yield items.sublist(i, end);
}
}
void main() {
final data = List.generate(7, (i) => i + 1);
for (final chunk in chunked(data, 3)) {
print(chunk);
}
}
chunked walks the list in steps of size, yielding a
sublist each time and clamping the last chunk to the list length. The result is
three chunks: two of size three and a final chunk of size one. Nonpositive sizes
are rejected with ArgumentError when iteration begins, since a
sync* body runs lazily. Because each chunk is produced on demand,
the whole set of chunks never has to exist at once.
$ dart run chunking.dart [1, 2, 3] [4, 5, 6] [7]
Source
Dart Language: Generators
Dart Language: Asynchronous programming
Dart Language: Iterables
Dart API: dart:async
Generators let you describe a sequence by how each value is produced rather than
by building a collection. Synchronous generators with sync* return
lazy Iterables, asynchronous generators with async*
return Streams, and yield* composes generators, including
recursively. The result is code that is both concise and memory efficient.
Author
List all Dart tutorials.