ZetCode

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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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

Source

Dart EfficientLengthIterable Documentation

This tutorial covered Dart's EfficientLengthIterable with practical examples demonstrating its benefits and usage patterns.

Author

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.