Dart Iterator
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 returnstrue
if there is another element available andfalse
if the end of the collection is reached. -
current
- Retrieves the current element of the iteration. This property returnsnull
ifmoveNext()
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.
Basic Iterator Usage
This example shows the fundamental way to use an iterator manually.
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
Custom Iterator Implementation
This example demonstrates creating a custom class that implements Iterator.
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
Iterable with Iterator
This example shows how to create a custom Iterable class that provides an iterator.
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
Iterator with Complex Objects
This example shows using an iterator with a collection of custom objects.
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.
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
.
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.
Iterator Utilities
This example demonstrates useful iterator utilities like takeWhile and skipWhile.
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
Source
This tutorial covered Dart's Iterator interface with practical examples showing basic usage, custom implementations, and common patterns.
Author
List all Dart tutorials.