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 returnstrueif there is another element available andfalseif the end of the collection is reached. -
current- Retrieves the current element of the iteration. This property returnsnullifmoveNext()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.