Watch the video (10 min)
On this page22 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Three Collections Side by Side
  5. 2. Order
  6. 3. Duplicates
  7. 4. Access: By Position, By Membership, By Key
  8. 5. Why contains Is Faster on a Set
  9. 6. Converting Between Collections
  10. 7. A List of Maps — List<Map<String, int>>
  11. 8. A Map of Lists — Map<String, List<String>>
  12. 9. Choosing: Three Questions
  13. 10. A Real-World Example
  14. Code Examples
  15. Common Beginner Mistakes
  16. Best Practices
  17. Mini Challenge
  18. Practice Questions
  19. Practice Answers
  20. Quick Revision
  21. Key Takeaways
  22. What's Next?

Lecture 51 — List vs Set vs Map

Category8 — Sets & Maps
Lecture051 of 140
Estimated video length10 min
TargetDart 3.12.2
LevelBeginner

You now know three collections. This lecture answers the question that comes before any code: which one fits the data — and how to combine them when one is not enough.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.


Prerequisites

Students should already understand, from Lectures 001–050:

  • the null-aware ?? operator (Lecture 014) and the ! assertion (Lecture 015)
  • lists: indexes, adding and removing, contains and indexOf (Lectures 039–042)
  • toList, toSet and sort (Lectures 043–046)
  • sets and set operations (Lectures 047–048)
  • maps, lookup by key, entries, keys and values (Lectures 049–050)

Connection With Previous Lectures

Almost any task can be solved with a list. Here is a price lookup built from two lists:

dart
void main() {
  var products = ['tea', 'milk', 'bread'];
  var prices = [120, 250, 90];
  print(prices[products.indexOf('milk')]);
}
output
250

It works — but the two lists must stay in step forever. Insert a product into one and forget the other, and every price after it is wrong. A map would have stored 'milk': 250 as one pair. Picking the right collection removes whole kinds of bugs before they are written.


Learning Objectives

By the end of this lecture you will be able to:

  • Compare lists, sets and maps on order, duplicates and access
  • Explain why contains on a set stays fast while on a list it slows as the list grows
  • Choose a collection by asking three questions about the data
  • Convert between lists, sets and maps
  • Build and read a List<Map<String, int>>
  • Build and read a Map<String, List<String>>
  • Handle the nullable value that a nested map lookup returns
  • Recognize the errors that come from treating one collection like another

1. Three Collections Side by Side

Analogy — a list is a queue at a counter: order matters and the same person can queue twice. A set is a guest list: each name appears once. A map is a phone book: you look up a name and get its number.

dart
void main() {
  List<String> queue = ['Ali', 'Sara', 'Ali'];
  Set<String> guests = queue.toSet();
  Map<String, int> ages = {'Ali': 20, 'Sara': 18};
  print(queue);
  print(guests);
  print(ages);
}
output
[Ali, Sara, Ali]
{Ali, Sara}
{Ali: 20, Sara: 18}

Square brackets for lists, curly braces for sets, curly braces with key: value pairs for maps.


2. Order

All three remember the order in which values were added. Only a list gives you positions you can use, though: a set and a map print in order, but you cannot ask for "element number 2".

dart
void main() {
  var tags = {'dart', 'code', 'fun'};
  var stock = {'tea': 5, 'milk': 2};
  print(tags.first);
  print(stock.keys.first);
}
output
dart
tea

3. Duplicates

A list keeps repeats. A set ignores them — add returns false. A map's keys are unique: writing to an existing key replaces its value.

dart
void main() {
  var marks = [80, 80];
  marks.add(80);
  var tags = {'dart'};
  print(tags.add('dart'));
  var stock = {'tea': 5};
  stock['tea'] = 9;
  print(marks);
  print(tags);
  print(stock);
}
output
false
[80, 80, 80]
{dart}
{tea: 9}

4. Access: By Position, By Membership, By Key

A list answers "what is at index 1?". A set answers "is this in here?". A map answers "what belongs to this key?" — and gives null when the key is missing.

dart
void main() {
  var names = ['Ali', 'Sara'];
  var guests = {'Ali', 'Sara'};
  var ages = {'Ali': 20};
  print(names[1]);
  print(guests.contains('Sara'));
  print(ages['Ali']);
  print(ages['Zayan']);
}
output
Sara
true
20
null

Because a key can be missing, ages['Ali'] has type int?, not int.


5. Why contains Is Faster on a Set

A list has no shortcut: contains looks at element 0, then 1, then 2, until it finds the value or reaches the end. This loop does the same thing and counts the checks:

dart
void main() {
  var visitors = ['Ali', 'Sara', 'Zayan', 'Hina', 'Omar'];
  var checks = 0;
  for (final name in visitors) {
    checks++;
    if (name == 'Omar') break;
  }
  print('Checked $checks names');
}
output
Checked 5 names

Twice as many names means up to twice as many checks. A set works differently: it files each value in a slot calculated from the value itself, so contains goes almost straight to the right slot — about the same work for ten names or a million. A map's containsKey and [key] use the same trick. (containsValue does not: values are not filed that way, so it checks them one by one, like a list.)

For five names you will never notice the difference. For a large collection searched many times — every visitor checked against every banned name, say — choose a set or a map.


6. Converting Between Collections

toSet() removes duplicates (keeping first-seen order); toList() turns a set back into a list so you can sort it or use indexes. A loop with ?? builds a map that counts.

dart
void main() {
  var visits = ['Sara', 'Ali', 'Sara', 'Zayan', 'Ali'];

  var unique = visits.toSet();
  var sorted = unique.toList();
  sorted.sort();

  var counts = <String, int>{};
  for (final name in visits) {
    counts[name] = (counts[name] ?? 0) + 1;
  }

  print(unique);
  print(sorted);
  print(counts);
  print(counts.keys.toList());
}
output
{Sara, Ali, Zayan}
[Ali, Sara, Zayan]
{Sara: 2, Ali: 2, Zayan: 1}
[Sara, Ali, Zayan]

counts[name] ?? 0 reads "the count so far, or 0 if this name is new".


7. A List of Maps — List<Map<String, int>>

Each map is one record; the list holds many records in order. Here each map is one day's temperatures, and the list is the week.

dart
void main() {
  List<Map<String, int>> week = [
    {'min': 12, 'max': 24},
    {'min': 14, 'max': 27},
    {'min': 11, 'max': 22},
  ];
  print(week[1]['max']);
  for (final day in week) {
    var range = day['max']! - day['min']!;
    print('Range: $range');
  }
}
output
27
Range: 12
Range: 13
Range: 11

Read week[1]['max'] in two steps: week[1] is a map, then ['max'] looks up a key in it. The result is int?, so before doing arithmetic we use ! — safe here because every day has both keys.


8. A Map of Lists — Map<String, List<String>>

Each key names a group; its value is the list of members. Here: class → students.

dart
void main() {
  Map<String, List<String>> classes = {
    'A': ['Ali', 'Sara'],
    'B': ['Zayan'],
  };
  classes['B']!.add('Hina');
  classes.putIfAbsent('C', () => []).add('Omar');

  for (final entry in classes.entries) {
    print('${entry.key}: ${entry.value}');
  }
}
output
A: [Ali, Sara]
B: [Zayan, Hina]
C: [Omar]

classes['B']! gets the existing list so we can add to it. putIfAbsent('C', () => []) returns the list for 'C', creating an empty one first if the key is new — so adding to a brand-new group needs no if.


9. Choosing: Three Questions

Ask these about your data, in order:

QuestionIf yes, use
Do you look values up by a name or ID?Map
Must every value be unique, and is the main question "is it in?"Set
Do order, positions or repeats matter?List
ScenarioChoice
A playlist (order matters, a song may repeat)List<String>
Tags on a post, each onceSet<String>
Product name → priceMap<String, int>
Marks of one student, in test orderList<int>
Class → its studentsMap<String, List<String>>
Each day's min and max temperatureList<Map<String, int>>

When a record grows many different fields — a name, an age, a list of marks — a map starts to strain. We will build proper types for records like that in Lecture 052, with classes.


10. A Real-World Example

A checkout: scanned items arrive as a list (order and repeats are real), prices live in a map, quantities are counted into another map, and a set counts different items.

dart
void main() {
  Map<String, int> prices = {'tea': 120, 'milk': 250, 'bread': 90};
  List<String> scanned = ['milk', 'tea', 'milk', 'bread', 'milk'];

  var quantities = <String, int>{};
  for (final item in scanned) {
    quantities[item] = (quantities[item] ?? 0) + 1;
  }

  var total = 0;
  for (final entry in quantities.entries) {
    var cost = prices[entry.key]! * entry.value;
    total += cost;
    print('${entry.key} x${entry.value} = $cost');
  }
  print('Different items: ${scanned.toSet().length}');
  print('Total: $total');
}
output
milk x3 = 750
tea x1 = 120
bread x1 = 90
Different items: 3
Total: 960

Code Examples

Example 1 — The Same Numbers, Three Ways

dart
void main() {
  var rolls = [3, 5, 3, 6];
  print(rolls);
  print(rolls.toSet());
  print({'first': rolls.first, 'last': rolls.last});
}
output
[3, 5, 3, 6]
{3, 5, 6}
{first: 3, last: 6}

Example 2 — Removing Duplicates

dart
void main() {
  var emails = ['a@x.com', 'b@x.com', 'a@x.com'];
  var unique = emails.toSet().toList();
  print(unique);
  print('${emails.length} sent, ${unique.length} people');
}
output
[a@x.com, b@x.com]
3 sent, 2 people

Example 3 — Sorting a Set Through a List

dart
void main() {
  var cities = {'Lahore', 'Karachi', 'Islamabad'};
  var list = cities.toList();
  list.sort();
  print(list);
  print(list[0]);
}
output
[Islamabad, Karachi, Lahore]
Islamabad

Example 4 — A Map Key Is Replaced, Not Repeated

dart
void main() {
  var scores = {'Ali': 10};
  scores['Ali'] = 15;
  scores['Sara'] = 12;
  print(scores);
  print(scores.length);
}
output
{Ali: 15, Sara: 12}
2

Example 5 — Same Question, List and Set

dart
void main() {
  var bannedList = ['spam1', 'spam2', 'spam3'];
  var bannedSet = bannedList.toSet();
  print(bannedList.contains('spam3'));
  print(bannedSet.contains('spam3'));
}
output
true
true

Same answer; the set gets there without walking the whole collection.

Example 6 — Two Parallel Lists Into One Map

dart
void main() {
  var products = ['tea', 'milk', 'bread'];
  var prices = [120, 250, 90];
  var priceOf = <String, int>{};
  for (var i = 0; i < products.length; i++) {
    priceOf[products[i]] = prices[i];
  }
  print(priceOf);
  print(priceOf['milk']);
}
output
{tea: 120, milk: 250, bread: 90}
250

Example 7 — Student Marks: Map<String, List<int>>

dart
void main() {
  var marks = {
    'Ali': [80, 75, 85],
    'Sara': [92, 88, 95],
  };
  for (final entry in marks.entries) {
    var total = 0;
    for (final mark in entry.value) {
      total += mark;
    }
    print('${entry.key}: ${total / entry.value.length}');
  }
}
output
Ali: 80.0
Sara: 91.66666666666667

Example 8 — Shopping Lines: List<Map<String, int>>

dart
void main() {
  var lines = [
    {'price': 120, 'qty': 2},
    {'price': 250, 'qty': 1},
    {'price': 90, 'qty': 3},
  ];
  var total = 0;
  for (final line in lines) {
    total += line['price']! * line['qty']!;
  }
  print('Total: $total');
}
output
Total: 760

Example 9 — Bank: Balances in a Map, Accounts Seen in a Set

dart
void main() {
  var balances = {'Ali': 500, 'Sara': 300};
  var deposits = ['Ali', 'Sara', 'Ali'];
  var active = <String>{};
  for (final name in deposits) {
    balances[name] = balances[name]! + 100;
    active.add(name);
  }
  print(balances);
  print('Active accounts: ${active.length}');
}
output
{Ali: 700, Sara: 400}
Active accounts: 2

Example 10 — Temperature Readings per City

dart
void main() {
  Map<String, List<int>> readings = {};
  readings.putIfAbsent('Lahore', () => []).add(31);
  readings.putIfAbsent('Murree', () => []).add(14);
  readings.putIfAbsent('Lahore', () => []).add(34);
  print(readings);
  print('Lahore readings: ${readings['Lahore']!.length}');
}
output
{Lahore: [31, 34], Murree: [14]}
Lahore readings: 2

Common Beginner Mistakes

1. Using an index on a set

dart
void main() {
  var guests = {'Ali', 'Sara'};
  // print(guests[0]); // ✗ a set has no positions
}
output
Error: The operator '[]' isn't defined for the type 'Set<String>'.

Use first, contains, or convert with toList() when you truly need positions.

2. Treating a map lookup as non-nullable

dart
void main() {
  var prices = {'tea': 120};
  // int price = prices['tea']; // ✗ the key might be missing
}
output
Error: A value of type 'int?' can't be assigned to a variable of type 'int'.

Use prices['tea'] ?? 0 for a default, or ! only when you are sure the key exists.

3. Forgetting that a nested lookup is nullable too

dart
void main() {
  var week = [
    {'min': 12, 'max': 24},
  ];
  // print(week[0]['max'] + 1); // ✗ ['max'] gives int?
}
output
Error: Operator '+' cannot be called on 'int?' because it is potentially null.

4. Collecting unique values in a list

dart
void main() {
  var visitors = <String>[];
  for (final name in ['Ali', 'Sara', 'Ali']) {
    visitors.add(name);
  }
  print('Unique visitors: ${visitors.length}');
}
output
Unique visitors: 3

No error — just a wrong answer. A Set<String> would have printed 2.

5. Keeping two lists in step instead of one map

Parallel lists like products and prices break silently when one changes. Store pairs in a Map<String, int>.


Best Practices

  • Ask the three questions — key lookup? unique? order or repeats? — before writing the type.
  • Use a set or a map whenever you search a large collection many times.
  • Prefer ?? default over ! on map lookups unless the key is guaranteed.
  • Use toSet() to remove duplicates and toList() when you need sorting or indexes.
  • Use putIfAbsent(key, () => []) to add to a group in a Map<String, List<…>>.
  • Write nested types out in full (Map<String, List<String>>) for empty collections.
  • Keep nesting shallow; two levels is usually the comfortable limit.

Mini Challenge

Given the words ['dart', 'is', 'fun', 'dart', 'is', 'dart'], print how many different words there are, then how many times each word appears.

output
Different words: 3
{dart: 3, is: 2, fun: 1}

Try it yourself before opening the solution.

Show solution
dart
void main() {
  var words = ['dart', 'is', 'fun', 'dart', 'is', 'dart'];
  print('Different words: ${words.toSet().length}');
  var counts = <String, int>{};
  for (final word in words) {
    counts[word] = (counts[word] ?? 0) + 1;
  }
  print(counts);
}
output
Different words: 3
{dart: 3, is: 2, fun: 1}

Practice Questions

Beginner

  1. Which collection would you choose for each, and why: a to-do list in order, the countries a traveller has visited, a student's ID → name?
  2. Turn [4, 2, 4, 9, 2] into a set and print it.
  3. Create the map {'Ali': 20}, set 'Ali' to 21, and print the map and its length.
  4. Print the value for the missing key 'Zayan' in {'Ali': 20}, using ?? 0.

Application

  1. Turn the set {'mango', 'apple', 'kiwi'} into a sorted list and print it.
  2. Build a map from names = ['Ali', 'Sara'] and ages = [20, 18] and print it.
  3. Given [{'price': 50, 'qty': 4}, {'price': 30, 'qty': 2}], print the total cost.
  4. Start with {'A': ['Ali']}. Add 'Sara' to group 'A' and 'Zayan' to a new group 'B', then print the map.

Challenge

  1. Given [85, 42, 85, 91, 42, 70], print the number of different marks and the highest one.
  2. Given the votes ['red', 'blue', 'red', 'green', 'red', 'blue'], count the votes per colour into a map and print the colour with the most votes.

Practice Answers

Question 1 — Solution

To-do list → List<String> (order matters). Countries visited → Set<String> (each once, "have I been there?"). ID → name → Map<int, String> (lookup by key).

Question 2 — Solution

dart
void main() {
  print([4, 2, 4, 9, 2].toSet());
}
output
{4, 2, 9}

Question 3 — Solution

dart
void main() {
  var ages = {'Ali': 20};
  ages['Ali'] = 21;
  print(ages);
  print(ages.length);
}
output
{Ali: 21}
1

Question 4 — Solution

dart
void main() {
  var ages = {'Ali': 20};
  print(ages['Zayan'] ?? 0);
}
output
0

Question 5 — Solution

dart
void main() {
  var fruits = {'mango', 'apple', 'kiwi'}.toList();
  fruits.sort();
  print(fruits);
}
output
[apple, kiwi, mango]

Question 6 — Solution

dart
void main() {
  var names = ['Ali', 'Sara'];
  var ages = [20, 18];
  var ageOf = <String, int>{};
  for (var i = 0; i < names.length; i++) {
    ageOf[names[i]] = ages[i];
  }
  print(ageOf);
}
output
{Ali: 20, Sara: 18}

Question 7 — Solution

dart
void main() {
  var lines = [
    {'price': 50, 'qty': 4},
    {'price': 30, 'qty': 2},
  ];
  var total = 0;
  for (final line in lines) {
    total += line['price']! * line['qty']!;
  }
  print(total);
}
output
260

Question 8 — Solution

dart
void main() {
  Map<String, List<String>> groups = {
    'A': ['Ali'],
  };
  groups['A']!.add('Sara');
  groups.putIfAbsent('B', () => []).add('Zayan');
  print(groups);
}
output
{A: [Ali, Sara], B: [Zayan]}

Question 9 — Solution

dart
void main() {
  var marks = [85, 42, 85, 91, 42, 70];
  var different = marks.toSet().toList();
  different.sort();
  print('Different marks: ${different.length}');
  print('Highest: ${different.last}');
}
output
Different marks: 4
Highest: 91

After sorting, the highest mark is the last element.

Question 10 — Solution

dart
void main() {
  var votes = ['red', 'blue', 'red', 'green', 'red', 'blue'];
  var counts = <String, int>{};
  for (final colour in votes) {
    counts[colour] = (counts[colour] ?? 0) + 1;
  }
  var winner = '';
  var most = 0;
  for (final entry in counts.entries) {
    if (entry.value > most) {
      most = entry.value;
      winner = entry.key;
    }
  }
  print(counts);
  print('Winner: $winner with $most votes');
}
output
{red: 3, blue: 2, green: 1}
Winner: red with 3 votes

Quick Revision

  1. Which of the three collections gives you positions?
  2. What does a set do with a duplicate? What does a map do with an existing key?
  3. Why does ages['Ali'] have type int??
  4. Why does contains on a long list get slower, and why doesn't it on a set?
  5. How do you remove duplicates from a list?
  6. In week[1]['max'], what does each pair of brackets look up?
  7. What does putIfAbsent(key, () => []) return?
  8. Which collection fits "product name → price"?

Key Takeaways

  • List: ordered, positions, repeats allowed. Set: unique values, "is it in?". Map: unique keys, each with a value, looked up by key.
  • All three keep insertion order, but only a list has indexes.
  • contains on a list checks elements one by one; a set (and a map's keys) jumps almost straight to the answer.
  • A map lookup returns a nullable value — handle it with ?? or, when certain, !.
  • toSet() removes duplicates; toList() brings back sorting and indexes.
  • List<Map<String, int>> holds records in order; Map<String, List<String>> holds groups.
  • Choose by asking: lookup by key? unique? order or repeats?

What's Next?

Lecture 52 — OOP Fundamentals. Maps and lists carried our records this far, but a record like a student — name, age, marks — deserves a type of its own. Category 9 begins with the ideas behind object-oriented programming: describing things as objects with their own data and behaviour.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.