Iterables and Iteration

In Dart, an Iterable is a collection of elements that can be accessed sequentially. Think of it like a playlist of songs - you can go through each song one by one, but you might not be able to jump directly to song #5 without going through the previous ones first.

What is an Iterable?

An Iterable is an abstract class that represents a collection of objects that can be iterated over. Common examples include:

  • Lists
  • Sets
  • Maps (when iterating over keys, values, or entries)
  • Strings (as a sequence of characters)
// Examples of Iterables
List<int> numbers = [1, 2, 3, 4, 5];
Set<String> fruits = {'apple', 'banana', 'orange'};
String message = "Hello";

// All of these are Iterables!

Basic Iteration with for-in Loop

The most common way to iterate through an Iterable is using a for-in loop:

List<String> colors = ['red', 'green', 'blue'];

for (String color in colors) {
  print('I like $color');
}
// Output:
// I like red
// I like green
// I like blue

Iterator Pattern

Behind the scenes, Dart uses the Iterator pattern. Every Iterable has an iterator property that returns an Iterator object:

List<int> numbers = [10, 20, 30];
Iterator<int> iterator = numbers.iterator;

while (iterator.moveNext()) {
  print(iterator.current);
}
// Output: 10, 20, 30

Useful Iterable Methods

Iterables come with many built-in methods that make working with collections easier:

Transform and Filter

List<int> numbers = [1, 2, 3, 4, 5];

// map() - transform each element
Iterable<int> doubled = numbers.map((n) => n * 2);
print(doubled.toList()); // [2, 4, 6, 8, 10]

// where() - filter elements
Iterable<int> evenNumbers = numbers.where((n) => n % 2 == 0);
print(evenNumbers.toList()); // [2, 4]

Check and Find

List<String> fruits = ['apple', 'banana', 'cherry'];

// Check if any/every element matches a condition
bool hasLongName = fruits.any((fruit) => fruit.length > 6);
print(hasLongName); // false

bool allHaveVowels = fruits.every((fruit) => fruit.contains('a'));
print(allHaveVowels); // false

// Find elements
String? found = fruits.firstWhere((fruit) => fruit.startsWith('b'), 
                                 orElse: () => 'not found');
print(found); // banana

Reduce and Fold

List<int> numbers = [1, 2, 3, 4, 5];

// reduce() - combine all elements into a single value
int sum = numbers.reduce((a, b) => a + b);
print(sum); // 15

// fold() - like reduce but with an initial value
int product = numbers.fold(1, (prev, element) => prev * element);
print(product); // 120

Lazy Evaluation

One cool thing about Iterables is that many operations are lazy - they don't do the work until you actually need the results:

List<int> numbers = [1, 2, 3, 4, 5];

// This doesn't actually do any multiplication yet!
Iterable<int> doubled = numbers.map((n) => n * 2);

// The multiplication happens when we iterate
for (int value in doubled) {
  print(value); // Now the multiplication happens
}

Converting Between Types

You can easily convert between different collection types:

List<int> list = [1, 2, 3, 2, 1];
Set<int> uniqueSet = list.toSet(); // {1, 2, 3}
List<int> backToList = uniqueSet.toList(); // [1, 2, 3]

// Convert to other iterables
Iterable<String> strings = list.map((n) => n.toString());

Common Iterable Properties

PropertyDescriptionExample
lengthNumber of elements[1,2,3].length → 3
isEmptyTrue if no elements[].isEmpty → true
isNotEmptyTrue if has elements[1].isNotEmpty → true
firstFirst element[1,2,3].first → 1
lastLast element[1,2,3].last → 3

Remember: Iterables are the foundation of working with collections in Dart. They provide a consistent way to work with different types of data collections, making your code more flexible and reusable!