Skip to main content

Collection Utilities

Dart collections are equipped with powerful functional utility methods. In addition, helper techniques like chunking, partitioning, zip, and grouping enable advanced data processing pipelines.


1. Fundamental Transformation & Filtering​

map() — Transform Each Element​

final numbers = [1, 2, 3, 4];
final squared = numbers.map((n) => n * n).toList(); // [1, 4, 9, 16]

where() & whereType() — Filter​

final mixed = [1, 'apple', 2, 'banana', 3.14];
final intsOnly = mixed.whereType<int>().toList(); // [1, 2]

expand() — FlatMap / Flattening​

final pairs = [[1, 2], [3, 4], [5, 6]];
final flattened = pairs.expand((list) => list).toList(); // [1, 2, 3, 4, 5, 6]

2. Accumulation: reduce() vs fold()​

  • reduce() uses the first element as the initial accumulator. Throws error if collection is empty.
  • fold() accepts an explicit initial value and works on empty collections.
final numbers = [10, 20, 30];

// reduce
final sumReduce = numbers.reduce((acc, val) => acc + val); // 60

// fold (safely handles empty lists and type conversions)
final emptyList = <int>[];
final sumFold = emptyList.fold(0, (acc, val) => acc + val); // 0

// Type change with fold (List<int> -> Map<String, int>)
final stats = numbers.fold<Map<String, int>>(
{'sum': 0, 'count': 0},
(acc, n) {
acc['sum'] = acc['sum']! + n;
acc['count'] = acc['count']! + 1;
return acc;
},
);

3. Advanced Utilities: Grouping, Chunking & Partitioning​

Grouping (groupBy)​

Group items by a key function using standard Dart:

final words = ['apple', 'apricot', 'banana', 'blueberry', 'cherry'];

final Map<String, List<String>> grouped = {};
for (final word in words) {
final key = word[0];
grouped.putIfAbsent(key, () => []).add(word);
}
// {a: [apple, apricot], b: [banana, blueberry], c: [cherry]}

Or using package:collection:

import 'package:collection/collection.dart';

final groupedByLen = groupBy(words, (w) => w.length);
// {5: [apple], 7: [apricot], 6: [banana, cherry], 9: [blueberry]}

Chunking / Batching​

Splitting a list into chunks of fixed size:

List<List<T>> chunk<T>(List<T> list, int chunkSize) {
return [
for (var i = 0; i < list.length; i += chunkSize)
list.sublist(i, (i + chunkSize > list.length) ? list.length : i + chunkSize)
];
}

void main() {
final numbers = [1, 2, 3, 4, 5, 6, 7, 8];
print(chunk(numbers, 3)); // [[1, 2, 3], [4, 5, 6], [7, 8]]
}

Partitioning​

Splitting a collection into two lists based on a predicate:

(List<T> match, List<T> nonMatch) partition<T>(Iterable<T> iterable, bool Function(T) test) {
final match = <T>[];
final nonMatch = <T>[];
for (final element in iterable) {
if (test(element)) {
match.add(element);
} else {
nonMatch.add(element);
}
}
return (match, nonMatch);
}

void main() {
final numbers = [1, 2, 3, 4, 5, 6];
final (evens, odds) = partition(numbers, (n) => n.isEven);
print('Evens: $evens, Odds: $odds'); // Evens: [2, 4, 6], Odds: [1, 3, 5]
}

Zipping Two Iterables​

Combining elements of two iterables pairwise:

Iterable<(A, B)> zip<A, B>(Iterable<A> a, Iterable<B> b) sync* {
final itA = a.iterator;
final itB = b.iterator;
while (itA.moveNext() && itB.moveNext()) {
yield (itA.current, itB.current);
}
}

void main() {
final keys = ['id', 'name', 'role'];
final values = [101, 'Alice', 'Admin'];

final zipped = zip(keys, values).toList();
print(zipped); // [(id, 101), (name, Alice), (role, Admin)]
}

4. Lazy Generator Functions (sync* & async*)​

Dart generators allow custom iterative algorithms with zero memory allocation overhead.

// Synchronous generator
Iterable<int> range(int start, int end, [int step = 1]) sync* {
for (var i = start; i < end; i += step) {
yield i;
}
}

void main() {
for (final n in range(0, 10, 2)) {
print(n); // 0, 2, 4, 6, 8
}
}

Summary​

GoalTechnique / Method
Filter elementswhere(), whereType()
Transform 1-to-1map()
Transform 1-to-N & flattenexpand()
Combine into single resultfold() (preferred over reduce())
Group elementsgroupBy or putIfAbsent
Split into batchesSublist chunking
Combine 2 lists into pairsCustom zip generator or IterableZip