Lesson 2 of 77 beginner

Collections, Iterables & Equality

Data Structures That Show Up in Every Interview

Open interactive version (quiz + challenge)

Real-world analogy

Collections are like different kinds of bags. A List is a numbered shopping bag where order matters. A Set is a bag of unique marbles — try to add a duplicate and it just bounces off. A Map is a filing cabinet where every drawer has a label (key) and holds one thing (value).

What is it?

Dart collections (List, Set, Map) are the core data structures you'll use in every Flutter app and every interview. Understanding their performance characteristics, equality behavior, and functional methods (map/where/reduce) is essential for writing efficient code and answering algorithm questions.

Real-world relevance

In a SaaS collaboration app, you use Map to store workspace data by ID, Set to track unique online users, and List for ordered chat messages. The .where() method filters messages by channel, .map() transforms API responses into UI models, and proper equality ensures BLoC state changes trigger rebuilds correctly.

Key points

Code example

// Collections — Interview Essentials

// List — ordered, indexed
final List<String> fruits = ['apple', 'banana', 'cherry'];
fruits.add('date');                    // [apple, banana, cherry, date]
fruits.removeAt(1);                    // [apple, cherry, date]
final sliced = fruits.sublist(0, 2);   // [apple, cherry]

// Set — unique values, O(1) lookup
final Set<int> ids = {1, 2, 3, 3, 3}; // {1, 2, 3} — duplicates ignored
ids.contains(2);                       // true — O(1)
final unique = [1,1,2,2,3].toSet().toList(); // [1, 2, 3]

// Map — key-value pairs
final Map<String, int> scores = {'math': 95, 'science': 87};
scores['english'] = 91;               // Add entry
final math = scores['math'] ?? 0;     // Safe access with fallback
scores.entries.forEach((e) => print('${e.key}: ${e.value}'));

// Functional operations (lazy by default)
final numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

final doubled = numbers.map((n) => n * 2);           // Lazy Iterable
final evens = numbers.where((n) => n.isEven);         // Lazy Iterable
final sum = numbers.reduce((a, b) => a + b);          // 55
final product = numbers.fold<int>(1, (a, b) => a * b); // Works on empty too

// Force evaluation
final doubledList = doubled.toList(); // [2, 4, 6, 8, ...]

// Spread and collection if/for
final combined = [...fruits, ...['extra1', 'extra2']];
final bool showAdmin = true;
final menu = [
  'Home',
  'Profile',
  if (showAdmin) 'Admin Panel',
];
final widgets = [for (var item in menu) 'MenuItem: $item'];

// Equality
class User {
  final String id;
  final String name;
  const User(this.id, this.name);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is User && other.id == id && other.name == name;

  @override
  int get hashCode => id.hashCode ^ name.hashCode;
}

final u1 = User('1', 'Alice');
final u2 = User('1', 'Alice');
print(u1 == u2);           // true (value equality)
print(identical(u1, u2));  // false (different instances)

Line-by-line walkthrough

  1. 1. Creating an ordered List of strings with three items
  2. 2. Adding 'date' to the end of the list
  3. 3. Removing the item at index 1 (banana)
  4. 4. Getting a sublist from index 0 to 2 (exclusive)
  5. 5. Creating a Set — notice the duplicate 3s are automatically removed
  6. 6. Checking membership in O(1) time — much faster than List.contains()
  7. 7. Converting a list with duplicates to a Set and back to get unique values
  8. 8. Creating a Map with String keys and int values
  9. 9. Adding a new key-value pair using bracket notation
  10. 10. Safe access with ?? fallback — if key doesn't exist, returns 0
  11. 11. Iterating over map entries with forEach
  12. 12. Functional chain: .map() transforms lazily, .where() filters lazily
  13. 13. .reduce() combines all elements — throws on empty list
  14. 14. Using .fold() with initial value 1 — safe on empty lists

Spot the bug

final list = const [3, 1, 4, 1, 5];
list.sort();

final map = <String, int>{};
map['a'] = 1;
final value = map['b'];
print(value.isEven);
Need a hint?
A const list can't be modified, and a missing map key returns null...
Show answer
Bug 1: list is const so .sort() throws at runtime — remove const to make it mutable. Bug 2: map['b'] returns null (key doesn't exist), so value is int? — calling .isEven on null crashes. Fix: print(value?.isEven ?? false) or check for null first.

Explain like I'm 5

Imagine you have three types of toy boxes. A List box has numbered slots — toy #1, toy #2, toy #3 — and you can have two identical toys. A Set box is magical — if you try to put in a toy you already have, it just bounces back out. A Map box has labeled drawers — the 'car' drawer, the 'doll' drawer — and each label opens to exactly one toy.

Fun fact

Dart's collection-if and collection-for syntax is unique among mainstream languages. Most languages require separate logic to conditionally include items in a list. Dart lets you do it inline, which is especially powerful when building widget trees in Flutter.

Hands-on challenge

Write a function that takes a List> of user JSON objects and returns a List of unique email addresses, sorted alphabetically. Use .map(), .toSet(), and .toList(). Handle null email fields gracefully.

More resources

Open interactive version (quiz + challenge) ← Back to course: Flutter Interview Mastery