Dart Iterator tutorial shows how to work with iterators in Dart using the Iterator interface.
last modified June 7, 2025
In Dart, the Iterator interface serves as a fundamental mechanism for accessing elements within a collection sequentially. It plays a crucial role in Dart’s iteration system, allowing developers to traverse lists, sets, maps, and other iterable structures efficiently.
The Iterator interface defines two essential methods:
moveNext() - Advances the iterator to the next element in the collection. It returns true if there is another element available and false if the end of the collection is reached.
current - Retrieves the current element of the iteration. This property returns null if moveNext() has not been called or if the iteration is complete.
In Dart, most collections implement the Iterable interface, which provides an iterator through the iterator property. This allows easy iteration over elements using a for-in loop, forEach() method, or manually managing the iterator using moveNext() and current.
The Iterator interface is particularly useful when working with asynchronous or custom collections, where controlling iteration explicitly can optimize performance and memory usage. By implementing Iterator, developers can create tailored traversal logic for specialized data structures.
This example shows the fundamental way to use an iterator manually.
main.dart
void main() { var numbers = [1, 2, 3, 4, 5]; var iterator = numbers.iterator;
while (iterator.moveNext()) { print(iterator.current); } }
We create an iterator from a list and use moveNext() to advance through elements. The current property gives the current element. This is how for-in loops work internally.
$ dart main.dart 1 2 3 4 5
This example demonstrates creating a custom class that implements Iterator.
main.dart
class Countdown implements Iterator<int> { int _current = 5;
@override int get current => _current;
@override bool moveNext() { if (_current > 0) { _current–; return true; } return false; } }
void main() { var countdown = Countdown();
while (countdown.moveNext()) { print(countdown.current); } }
We implement the Iterator interface with a countdown from 5 to 0. moveNext() decrements _current until it reaches 0. The current getter returns the value.
$ dart main.dart 4 3 2 1 0
This example shows how to create a custom Iterable class that provides an iterator.
main.dart
class FibonacciSequence implements Iterable<int> { final int count;
FibonacciSequence(this.count);
@override Iterator<int> get iterator => _FibonacciIterator(count); }
class _FibonacciIterator implements Iterator<int> { final int count; int _current = 0; int _next = 1; int _position = 0;
_FibonacciIterator(this.count);
@override int get current => _current;
@override bool moveNext() { if (_position < count) { var newNext = _current + _next; _current = _next; _next = newNext; _position++; return true; } return false; } }
void main() { var fib = FibonacciSequence(7);
for (var num in fib) { print(num); } }
We create a Fibonacci sequence generator. The Iterable provides an iterator that generates numbers. This allows the class to be used in for-in loops.
$ dart main.dart 1 1 2 3 5 8 13
This example shows using an iterator with a collection of custom objects.
main.dart
import ‘dart:core’;
class Book { final String title; final String author;
Book(this.title, this.author);
@override String toString() => ‘$title by $author’; }
class Library extends Iterable<Book> { final List<Book> _books = [];
void add(Book book) => _books.add(book);
@override Iterator<Book> get iterator => _books.iterator; }
void main() { var library = Library(); library.add(Book(‘Dart in Action’, ‘Manning’)); library.add(Book(‘Flutter in Action’, ‘Manning’)); library.add(Book(‘Effective Dart’, ‘Google’));
for (var book in library) { print(book); } }
We create a Library class that holds Books and extends Iterable. The iterator comes from the underlying List. This pattern is common in collection classes.
$ dart main.dart Dart in Action by Manning Flutter in Action by Manning Effective Dart by Google
In the next example, we will see how to extend the IterableMixin interface to create a custom iterable collection.
main.dart
import ‘dart:collection’;
class Book { final String title; final String author;
Book(this.title, this.author);
@override String toString() => ‘$title by $author’; }
class Library extends IterableMixin<Book> { final List<Book> _books = [];
void add(Book book) => _books.add(book);
@override Iterator<Book> get iterator => _books.iterator; }
void main() { final library = Library() ..add(Book(‘Dart in Action’, ‘Manning’)) ..add(Book(‘Flutter in Action’, ‘Manning’)) ..add(Book(‘Effective Dart’, ‘Google’));
// Using iterator directly final iterator = library.iterator; while (iterator.moveNext()) { print(iterator.current); }
// Using Iterable methods print(’\nBooks with “Action”:’); library .where((book) => book.title.contains(‘Action’)) .forEach(print);
print(’\nBook titles:’); library .map((book) => book.title.toUpperCase()) .forEach(print); }
We create a Library class that extends IterableMixin, which provides a default implementation of the Iterable interface. This allows us to focus on implementing the iterator method. The example shows how to use the iterator directly and also demonstrates common Iterable methods like where and map for filtering and transforming elements.
The next example shows how to fully satisfy the Iterable interface without relying on IterableMixin.
main.dart
class Book { final String title; final String author;
Book(this.title, this.author);
@override String toString() => ‘$title by $author’; }
class Library implements Iterable<Book> { final List<Book> _books = [];
void add(Book book) => _books.add(book);
@override bool any(bool Function(Book element) test) { for (var book in _books) { if (test(book)) return true; } return false; }
@override Iterable<R> cast<R>() sync* { for (var book in _books) { yield book as R; } }
@override bool contains(Object? element) => _books.contains(element);
@override Book elementAt(int index) => _books.elementAt(index);
@override bool every(bool Function(Book element) test) { for (var book in _books) { if (!test(book)) return false; } return true; }
@override Iterable<T> expand<T>(Iterable<T> Function(Book element) f) sync* { for (var book in _books) { yield* f(book); } }
@override Book get first => _books.first;
@override Book firstWhere(bool Function(Book element) test, {Book Function()? orElse}) { for (var book in _books) { if (test(book)) return book; } if (orElse != null) return orElse(); throw StateError(‘No element’); }
@override T fold<T>(T initialValue, T Function(T previousValue, Book element) combine) { var value = initialValue; for (var book in _books) { value = combine(value, book); } return value; }
@override Iterable<Book> followedBy(Iterable<Book> other) sync* { yield* _books; yield* other; }
@override void forEach(void Function(Book element) f) { for (var book in _books) { f(book); } }
@override bool get isEmpty => _books.isEmpty;
@override bool get isNotEmpty => _books.isNotEmpty;
@override Iterator<Book> get iterator => _books.iterator;
@override String join([String separator = ‘’]) => _books.join(separator);
@override Book get last => _books.last;
@override Book lastWhere(bool Function(Book element) test, {Book Function()? orElse}) { for (var i = _books.length - 1; i >= 0; i–) { if (test(_books[i])) return _books[i]; } if (orElse != null) return orElse(); throw StateError(‘No element’); }
@override int get length => _books.length;
@override Iterable<T> map<T>(T Function(Book e) f) sync* { for (var book in _books) { yield f(book); } }
@override Book reduce(Book Function(Book value, Book element) combine) { if (_books.isEmpty) throw StateError(‘No elements’); var value = _books.first; for (var i = 1; i < _books.length; i++) { value = combine(value, _books[i]); } return value; }
@override Book get single { if (_books.length == 1) return _books.first; if (_books.isEmpty) throw StateError(‘No elements’); throw StateError(‘More than one element’); }
@override Book singleWhere(bool Function(Book element) test, {Book Function()? orElse}) { Book? result; var found = false; for (var book in _books) { if (test(book)) { if (found) throw StateError(‘More than one element’); result = book; found = true; } } if (!found) { if (orElse != null) return orElse(); throw StateError(‘No element’); } return result!; }
@override Iterable<Book> skip(int count) sync* { for (var i = count; i < _books.length; i++) { yield _books[i]; } }
@override Iterable<Book> skipWhile(bool Function(Book value) test) sync* { var skipping = true; for (var book in _books) { if (skipping && test(book)) { continue; } else { skipping = false; yield book; } } }
@override Iterable<Book> take(int count) sync* { var taken = 0; for (var book in _books) { if (taken++ >= count) break; yield book; } }
@override Iterable<Book> takeWhile(bool Function(Book value) test) sync* { for (var book in _books) { if (!test(book)) break; yield book; } }
@override List<Book> toList({bool growable = true}) => List<Book>.from(_books, growable: growable);
@override Set<Book> toSet() => Set<Book>.from(_books);
@override Iterable<Book> where(bool Function(Book element) test) sync* { for (var book in _books) { if (test(book)) yield book; } }
@override Iterable<T> whereType<T>() sync* { for (var book in _books) { if (book is T) yield book as T; } } }
void main() { var library = Library(); library.add(Book(‘Dart in Action’, ‘Manning’)); library.add(Book(‘Flutter in Action’, ‘Manning’)); library.add(Book(‘Effective Dart’, ‘Google’));
for (var book in library) { print(book); }
print(’\nBooks with “Action”:’); for (var book in library.where((b) => b.title.contains(‘Action’))) { print(book); } }
All required Iterable methods are implemented manually. This allows the Library class to be used like any other iterable collection in Dart. The example demonstrates iterating over the library and filtering books based on a condition. This approach provides full control over the iteration process and allows for custom behavior while still adhering to the Iterable interface contract. This is useful when you need to create a custom collection type that behaves like a standard iterable but has specific requirements or optimizations.
This example demonstrates useful iterator utilities like takeWhile and skipWhile.
main.dart
void main() { var numbers = [1, 3, 5, 2, 4, 6, 8, 7];
print(‘Numbers while odd:’); var oddIterator = numbers.takeWhile((n) => n.isOdd).iterator; while (oddIterator.moveNext()) { print(oddIterator.current); }
print(’\nNumbers after first even:’); var afterEven = numbers.skipWhile((n) => n.isOdd).iterator; while (afterEven.moveNext()) { print(afterEven.current); } }
We use takeWhile to get elements until a condition fails, and skipWhile to skip elements until a condition fails. These methods return new iterators.
$ dart main.dart Numbers while odd: 1 3 5
Numbers after first even: 2 4 6 8 7
This tutorial covered Dart’s Iterator interface with practical examples showing basic usage, custom implementations, and common patterns.
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Dart tutorials.