Dart EfficientLengthIterable
last modified April 4, 2025
In Dart, EfficientLengthIterable is a mixin that marks iterables with efficient length access. It helps optimize operations that depend on length.
Classes implementing this mixin guarantee that their length getter is O(1). This enables performance optimizations in various collection operations.
Basic Usage
Here's a simple example demonstrating a custom EfficientLengthIterable.
import 'dart:collection'; class FixedList with IterableMixin<int>, EfficientLengthIterable { final List<int> _items; FixedList(this._items); @override Iterator<int> get iterator => _items.iterator; @override int get length => _items.length; } void main() { var numbers = FixedList([1, 2, 3, 4, 5]); print('Length: ${numbers.length}'); print('First: ${numbers.first}'); print('Last: ${numbers.last}'); }
We create a FixedList class that mixes in EfficientLengthIterable. This tells Dart that length is efficient. The class also implements required Iterable methods.
$ dart main.dart Length: 5 First: 1 Last: 5
Optimized Operations
EfficientLengthIterable enables optimizations for operations like isEmpty and isNotEmpty.
import 'dart:collection'; class Range with IterableMixin<int>, EfficientLengthIterable { final int start; final int end; Range(this.start, this.end); @override Iterator<int> get iterator => List.generate(end - start + 1, (i) => start + i).iterator; @override int get length => end - start + 1; } void main() { var range = Range(10, 20); print('Is empty: ${range.isEmpty}'); print('Is not empty: ${range.isNotEmpty}'); print('Length: ${range.length}'); }
The Range class represents a range of numbers with efficient length. Operations like isEmpty use the length directly rather than checking the iterator.
$ dart main.dart Is empty: false Is not empty: true Length: 11
Collection Operations
EfficientLengthIterable improves performance of collection operations like take and skip.
import 'dart:collection'; class EfficientList<T> with IterableMixin<T>, EfficientLengthIterable { final List<T> _items; EfficientList(this._items); @override Iterator<T> get iterator => _items.iterator; @override int get length => _items.length; } void main() { var items = EfficientList<String>(['a', 'b', 'c', 'd', 'e']); var taken = items.take(3); print('Taken: ${taken.toList()}'); var skipped = items.skip(2); print('Skipped: ${skipped.toList()}'); print('Element at 3: ${items.elementAt(3)}'); }
Operations like take and skip can optimize their behavior when they know the length is efficiently computable. This leads to better performance.
$ dart main.dart Taken: [a, b, c] Skipped: [c, d, e] Element at 3: d
Combining with Other Mixins
EfficientLengthIterable can be combined with other mixins for more functionality.
import 'dart:collection'; class CountingSet<T> with IterableMixin<T>, EfficientLengthIterable { final Map<T, int> _counts = {}; void add(T item) { _counts[item] = (_counts[item] ?? 0) + 1; } @override Iterator<T> get iterator => _counts.keys.iterator; @override int get length => _counts.length; int count(T item) => _counts[item] ?? 0; } void main() { var set = CountingSet<String>(); set.add('apple'); set.add('banana'); set.add('apple'); print('Items: ${set.toList()}'); print('Length: ${set.length}'); print('Apple count: ${set.count('apple')}'); }
This CountingSet combines EfficientLengthIterable with custom functionality. The length is efficiently computed from the underlying map's size.
$ dart main.dart Items: [apple, banana] Length: 2 Apple count: 2
Performance Comparison
Here's a comparison showing the performance benefit of EfficientLengthIterable.
import 'dart:collection'; import 'dart:math'; class SlowLengthIterable<T> with IterableMixin<T> { final List<T> _items; SlowLengthIterable(this._items); @override Iterator<T> get iterator => _items.iterator; @override int get length => _items.fold(0, (sum, _) => sum + 1); } class FastLengthIterable<T> with IterableMixin<T>, EfficientLengthIterable { final List<T> _items; FastLengthIterable(this._items); @override Iterator<T> get iterator => _items.iterator; @override int get length => _items.length; } void main() { var bigList = List.generate(1000000, (i) => i); var slow = SlowLengthIterable<int>(bigList); var fast = FastLengthIterable<int>(bigList); // Measure isEmpty var stopwatch = Stopwatch()..start(); print('Slow isEmpty: ${slow.isEmpty}'); stopwatch.stop(); print('Time: ${stopwatch.elapsedMicroseconds}μs'); stopwatch..reset()..start(); print('Fast isEmpty: ${fast.isEmpty}'); stopwatch.stop(); print('Time: ${stopwatch.elapsedMicroseconds}μs'); }
The FastLengthIterable completes isEmpty checks in constant time, while the slow version must iterate through all elements to compute length.
$ dart main.dart Slow isEmpty: false Time: 12500μs Fast isEmpty: false Time: 1μs
Best Practices
- Use for Known Lengths: Only implement when length is truly O(1).
- Combine with IterableMixin: Often used together for complete iterables.
- Document Behavior: Clearly document that length is efficient.
- Performance Critical Code: Especially useful in performance-sensitive sections.
Source
Dart EfficientLengthIterable Documentation
This tutorial covered Dart's EfficientLengthIterable with practical examples demonstrating its benefits and usage patterns.
Author
List all Dart tutorials.