Difference between yield and yield* in Dart

Short answer

  • yield returns values from an Iterable or a Stream.
  • yield* is used to recursively call its Iterable or Stream function.

Examples

Let's look an some examples from the Generator Functions - Flutter in Focus video. We'll just look at the Iterable example, but it's similar for Streams.

Form 1

Here is an Iterable that counts from start to finish.

Iterable<int> getRange(int start, int finish) sync* {
  for (int i = start; i <= finish; i++) {
    yield i;
  }
}

The yield keyword returns the next value on each iteration.

Form 2

Now let's refactor that function to be recursive. On the outside it still does the same thing as before:

Iterable<int> getRange(int start, int finish) sync* {
  if (start <= finish) {
    yield start;
    for (final val in getRange(start + 1, finish)) {
      yield val;
    }
  }
}

This works but it's hard to read and not very efficient because of the loop.

Form 3

Now let's refactor it again using yield* (pronounced "yield star"):

Iterable<int> getRange(int start, int finish) sync* {
  if (start <= finish) {
    yield start;
    yield* getRange(start + 1, finish);
  }
}

It's still recursive, but now it's easier to read and is more efficient.


I have created a dart pad link to help people experiment:

Yield* is used to yield a whole iterable one value at a time with out using a loop.

These 2 functions do exactly the same thing, generates an iterable based on the start and finish values.

Iterable<int> getRangeIteration(int start, int finish) sync* {
  for(int i = start; i<= finish; i++){
  yield i;
  }
}

Iterable<int> getRangeRecursive(int start, int finish) sync* {
  if (start <= finish) {
    yield start;
    yield* getRangeRecursive(start + 1, finish);
  }
}

In the first implementation (yield i) would imply type Iterable which matches the function's return type.

now if in the second implementation instead of

 yield* getRangeRecursive(start + 1, finish);

if we did

yield getRangeRecursive(start + 1, finish);

we will get a compiler error:

The type 'Iterable<Iterable<int>>' implied by the 'yield' expression must be assignable to 'Iterable<int>'.

as you can see the yield wraps the Iterable with another Iterable<>, which makes the type Iterable<Iterable>. Which does not match the return type of the function.

If we have to do recursion without using yield* we will have to do this:

Iterable<int> getRangeRecursiveWithOutYieldStar(int start, int finish) sync* {
  if (start <= finish) {
    yield start;
    for (final val in getRangeRecursiveWithOutYieldStar(start + 1, finish)){
    yield val;
    }
  }
}

Which is messy and inefficient.

So I feel like in my opinion yield* flattens another generator function.

A few good resources: Generator Functions - Flutter in Focus video

Medium article: What are sync*, async*, yield and yield* in Dart?


yield

It is used to emit values from a generator either async or sync.

Example:

Stream<int> str(int n) async* {
  for (var i = 1; i <= n; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

void main() {
  str(3).forEach(print);
}

Output:

1
2
3

yield*

It delegates the call to another generator and after that generator stops producing the values, it resumes generating its own values.

Example:

Stream<int> str(int n) async* {
  if (n > 0) {  
    await Future.delayed(Duration(seconds: 1));
    yield n;
    yield* str(n - 1);
  }
}

void main() {
  str(3).forEach(print);
}

Output:

3
2 
1 

Tags:

Dart

Flutter