Skip to content

Dart Collections — Lists, Sets, Maps, and Collection Operations

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Dart Collections. We cover key concepts, practical examples, and best practices to help you master this topic.

Dart collections provide type-safe data structures including List, Set, and Map with concise literal syntax, spread operators, collection-if, and a rich set of built-in operations for filtering, mapping, and reducing data.

What You Will Learn

  • Creating and manipulating Lists, Sets, and Maps
  • Collection literals and type inference
  • Spread (...) and null-aware spread (...?) operators
  • Collection-if for conditional inclusion
  • Common collection operations: map, where, reduce, fold
  • Sorting, filtering, and transforming data
  • Iterator and Iterable interface

Why It Matters

Collections are the primary way to work with groups of data in any program. Dart's collection system is designed to be concise and expressive. Collection literals with spread and collection-if reduce boilerplate compared to imperative building. The rich set of functional operations (map, where, reduce, fold) eliminate the need for explicit loops in most data transformation scenarios. Understanding collections is critical for Flutter development, where lists of widgets are built using collection operations.

Real-World Use

The DodaTech Flutter app uses collection operations extensively. A list of course modules is filtered with where, transformed with map, and sorted with sort. The search feature uses collection-if to conditionally include filters. The notification system uses set operations to merge and deduplicate notification IDs.

Learning Path

flowchart LR
  A[Dart Functions] --> B[Collections\nYou are here]
  B --> C[Dart Classes]
  style B fill:#f90,color:#fff

Lists

A List is an ordered collection of elements indexed by position. Dart lists are zero-indexed and growable by default:

void main() {
  // List literal
  var fruits = ['apple', 'banana', 'cherry'];
  print('Fruits: $fruits');
  print('First: ${fruits[0]}');
  print('Length: ${fruits.length}');

  // Adding and removing
  fruits.add('date');
  fruits.insert(1, 'blueberry');
  fruits.remove('banana');
  print('After mutations: $fruits');

  // Type-safe list
  List<int> numbers = [1, 2, 3, 4, 5];
  // numbers.add('text'); // COMPILE ERROR

  // Empty list with type
  var empty = <String>[];
  print('Empty list: $empty');
}

Output:

Fruits: [apple, banana, cherry]
First: apple
Length: 3
After mutations: [apple, blueberry, cherry, date]
Empty list: []

List literals use square brackets. The [] subscript accesses elements. add, insert, remove, and removeAt are common mutation methods.

Sets

A Set is an unordered collection of unique elements. Duplicate values are automatically ignored:

void main() {
  // Set literal
  var unique = {'apple', 'banana', 'apple', 'cherry'};
  print('Unique fruits: $unique');

  // Adding and checking
  unique.add('date');
  unique.add('banana'); // Already present, ignored
  print('Has apple? ${unique.contains('apple')}');
  print('Size: ${unique.length}');

  // Set operations
  var setA = {1, 2, 3, 4};
  var setB = {3, 4, 5, 6};

  print('Union: ${setA.union(setB)}');
  print('Intersection: ${setA.intersection(setB)}');
  print('Difference A-B: ${setA.difference(setB)}');
}

Output:

Unique fruits: {apple, banana, cherry}
Has apple? true
Size: 4
Union: {1, 2, 3, 4, 5, 6}
Intersection: {3, 4}
Difference A-B: {1, 2}

Sets use curly braces. The contains method is O(1) for hash-based sets. Set operations like union, intersection, and difference produce new sets without mutating the originals.

Maps

A Map is a collection of key-value pairs. Keys are unique, and each key maps to exactly one value:

void main() {
  // Map literal
  var scores = {
    'Alice': 95,
    'Bob': 87,
    'Charlie': 92,
  };
  print('Scores: $scores');

  // Accessing values
  print("Alice's score: ${scores['Alice']}");
  print("Unknown: ${scores['Unknown']}"); // null

  // Adding and updating
  scores['Diana'] = 88; // Add
  scores['Bob'] = 90; // Update
  print('Updated: $scores');

  // Checking existence
  print('Contains Alice? ${scores.containsKey('Alice')}');
  print('Contains value 100? ${scores.containsValue(100)}');

  // Iterating
  scores.forEach((name, score) {
    print('$name: $score');
  });
}

Output:

Scores: {Alice: 95, Bob: 87, Charlie: 92}
Alice's score: 95
Unknown: null
Updated: {Alice: 95, Bob: 90, Charlie: 92, Diana: 88}
Contains Alice? true
Contains value 100? false
Alice: 95
Bob: 90
...

Accessing a missing key returns null. The putIfAbsent method adds a value only if the key is not present. Entries can be iterated with forEach or with entries property.

Spread Operator

The spread operator ... and null-aware spread ...? expand collections into another collection:

void main() {
  var list1 = [1, 2, 3];
  var list2 = [4, 5, 6];

  // Spread operator
  var combined = [...list1, ...list2];
  print('Combined: $combined');

  // Multiple spreads
  var more = [0, ...list1, ...list2, 7];
  print('More: $more');

  // Null-aware spread
  List<int>? nullableList = null;
  var safe = [1, 2, ...?nullableList, 3];
  print('Safe: $safe');
}

Output:

Combined: [1, 2, 3, 4, 5, 6]
More: [0, 1, 2, 3, 4, 5, 6, 7]
Safe: [1, 2, 3]

The spread operator copies elements from one collection into another. The null-aware variant (...?) safely handles null collections by ignoring them instead of throwing.

Collection-If

Collection-if conditionally includes elements in a collection literal:

void main() {
  bool includeExtra = true;
  bool includeWarning = false;

  var items = [
    'Home',
    'Profile',
    if (includeExtra) 'Settings',
    if (includeWarning) 'Warning',
    'Logout',
  ];
  print('Menu items: $items');

  // Collection-for
  var numbers = [1, 2, 3];
  var doubled = [
    for (var n in numbers) n * 2,
  ];
  print('Doubled: $doubled');
}

Output:

Menu items: [Home, Profile, Settings, Logout]
Doubled: [2, 4, 6]

Collection-if is useful for conditionally showing UI elements in Flutter. Collection-for provides a concise way to transform elements inline.

Common Collection Operations

Dart's Iterable interface provides a rich set of methods for data processing:

void main() {
  var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

  // Filter with where
  var evens = numbers.where((n) => n.isEven);
  print('Evens: $evens');

  // Transform with map
  var squared = numbers.map((n) => n * n);
  print('First 3 squares: ${squared.take(3).toList()}');

  // Reduce to single value
  var sum = numbers.reduce((a, b) => a + b);
  print('Sum: $sum');

  // Fold (like reduce but with initial value)
  var product = numbers.fold(1, (acc, n) => acc * n);
  print('Product of first 5: ${numbers.take(5).fold(1, (a, b) => a * b)}');

  // Check conditions
  print('All positive? ${numbers.every((n) => n > 0)}');
  print('Any negative? ${numbers.any((n) => n < 0)}');

  // Find elements
  print('First > 5: ${numbers.firstWhere((n) => n > 5)}');
  print('Last < 5: ${numbers.lastWhere((n) => n < 5)}');
}

Output:

Evens: (2, 4, 6, 8, 10)
First 3 squares: [1, 4, 9]
Sum: 55
Product of first 5: 120
All positive? true
Any negative? false
First > 5: 6
Last < 5: 4

where returns a lazy iterable (not a list). Convert to list with .toList() if you need indexed access. reduce throws on empty collections; use fold when the collection may be empty.

Sorting

Lists can be sorted in place or produce a sorted copy:

void main() {
  var numbers = [3, 1, 4, 1, 5, 9, 2, 6];
  numbers.sort();
  print('Sorted: $numbers');

  var descending = [3, 1, 4, 1, 5, 9];
  descending.sort((a, b) => b.compareTo(a));
  print('Descending: $descending');

  // Sorting objects
  var people = [
    Person('Alice', 30),
    Person('Bob', 25),
    Person('Charlie', 35),
  ];
  people.sort((a, b) => a.age.compareTo(b.age));
  print('Youngest: ${people.first.name}');
  print('Oldest: ${people.last.name}');
}

class Person {
  final String name;
  final int age;
  Person(this.name, this.age);
}

Output:

Sorted: [1, 1, 2, 3, 4, 5, 6, 9]
Descending: [9, 5, 4, 3, 1, 1]
Youngest: Bob
Oldest: Charlie

The default sort() uses the natural ordering of elements. Custom comparators are functions that take two elements and return a negative, zero, or positive integer.

Iterating Collections

Dart provides multiple ways to iterate collections:

void main() {
  var fruits = ['apple', 'banana', 'cherry'];

  // For-in loop
  for (var fruit in fruits) {
    print(fruit);
  }

  // forEach with callback
  fruits.forEach((fruit) => print('Got: $fruit'));

  // While loop with iterator
  var iterator = fruits.iterator;
  while (iterator.moveNext()) {
    print('Via iterator: ${iterator.current}');
  }
}

Output:

apple
banana
cherry
Got: apple
Got: banana
Got: cherry
Via iterator: apple
Via iterator: banana
Via iterator: cherry

The for-in loop is the most idiomatic. forEach is useful for side effects. The manual iterator approach is rarely needed but demonstrates the underlying pattern.

Common Mistakes

  1. Modifying a list while iterating with for-in: Adding or removing elements during iteration throws ConcurrentModificationError. Collect items to modify separately, then apply changes after the loop.

  2. Using List.from when a cast is needed: List.from(iterable) creates a new list. For type conversion, use list.cast<NewType>() or List<NewType>.from(iterable).

  3. Confusing where with map: where filters elements (keeps or discards). map transforms each element. They are often chained: list.where(...).map(...).

  4. Forgetting that where returns a lazy iterable: Calling where does not evaluate the predicate until the result is iterated. Convert to list with .toList() if you need the results immediately or multiple times.

  5. Using == on lists for equality: == checks reference equality, not element equality. Use const [1, 2].equals([1, 2]) with the collection package or compare element by element.

Practice Questions

  1. What is the difference between List, Set, and Map?
  2. How does the spread operator ... differ from ...??
  3. When would you use reduce vs fold?
  4. Why does where return an Iterable instead of a List?
  5. Challenge: Write a Dart function that takes a list of integers and returns a map where keys are the unique values and values are the count of occurrences. Use collection operations without explicit loops.

Mini Project

Build a contact manager using collections:

  • Store contacts in a Map<String, String> (name to phone number)
  • Implement addContact, removeContact, and searchByName functions
  • Implement allContacts that returns a sorted list of names
  • Implement filterByPrefix that returns contacts whose names start with a given prefix
  • Implement mergeContacts that combines two contact maps using spread
  • Write tests for all functions

FAQ

Are Dart lists linked lists or array lists?

Dart's default List implementation is a growable array (like ArrayList in Java). For linked lists, use LinkedList from dart:collection.

Can I create a fixed-length list?

Yes. List.filled(5, 0) creates a fixed-length list. Attempting to add or remove elements throws an error.

How do I convert a Map's keys or values to a List?

Use map.keys.toList() and map.values.toList(). For a list of key-value pairs, use map.entries.toList().

What is the difference between `first` and `firstWhere`?

first returns the first element and throws if the list is empty. firstWhere accepts a predicate and a orElse fallback for safe access.

Can I use collection-if and spread in Sets and Maps?

Yes. Collection-if and spread work with all collection types: lists, sets, and maps.

What is Next

Now that you can work with collections, learn how to model data with classes. Proceed to Classes in Dart for constructors, fields, methods, and inheritance. Then explore Dart Inheritance for subclasses and polymorphism.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro