On this page22 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. Three Collections Side by Side
- 2. Order
- 3. Duplicates
- 4. Access: By Position, By Membership, By Key
- 5. Why contains Is Faster on a Set
- 6. Converting Between Collections
- 7. A List of Maps — List<Map<String, int>>
- 8. A Map of Lists — Map<String, List<String>>
- 9. Choosing: Three Questions
- 10. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 51 — List vs Set vs Map
| Category | 8 — Sets & Maps |
| Lecture | 051 of 140 |
| Estimated video length | 10 min |
| Target | Dart 3.12.2 |
| Level | Beginner |
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,
containsandindexOf(Lectures 039–042) toList,toSetandsort(Lectures 043–046)- sets and set operations (Lectures 047–048)
- maps, lookup by key,
entries,keysandvalues(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:
void main() {
var products = ['tea', 'milk', 'bread'];
var prices = [120, 250, 90];
print(prices[products.indexOf('milk')]);
}250It 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
containson 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.
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);
}[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".
void main() {
var tags = {'dart', 'code', 'fun'};
var stock = {'tea': 5, 'milk': 2};
print(tags.first);
print(stock.keys.first);
}dart
tea3. 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.
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);
}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.
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']);
}Sara
true
20
nullBecause 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:
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');
}Checked 5 namesTwice 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.
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());
}{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.
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');
}
}27
Range: 12
Range: 13
Range: 11Read 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.
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}');
}
}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:
| Question | If 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 |
| Scenario | Choice |
|---|---|
| A playlist (order matters, a song may repeat) | List<String> |
| Tags on a post, each once | Set<String> |
| Product name → price | Map<String, int> |
| Marks of one student, in test order | List<int> |
| Class → its students | Map<String, List<String>> |
| Each day's min and max temperature | List<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.
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');
}milk x3 = 750
tea x1 = 120
bread x1 = 90
Different items: 3
Total: 960Code Examples
Example 1 — The Same Numbers, Three Ways
void main() {
var rolls = [3, 5, 3, 6];
print(rolls);
print(rolls.toSet());
print({'first': rolls.first, 'last': rolls.last});
}[3, 5, 3, 6]
{3, 5, 6}
{first: 3, last: 6}Example 2 — Removing Duplicates
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');
}[a@x.com, b@x.com]
3 sent, 2 peopleExample 3 — Sorting a Set Through a List
void main() {
var cities = {'Lahore', 'Karachi', 'Islamabad'};
var list = cities.toList();
list.sort();
print(list);
print(list[0]);
}[Islamabad, Karachi, Lahore]
IslamabadExample 4 — A Map Key Is Replaced, Not Repeated
void main() {
var scores = {'Ali': 10};
scores['Ali'] = 15;
scores['Sara'] = 12;
print(scores);
print(scores.length);
}{Ali: 15, Sara: 12}
2Example 5 — Same Question, List and Set
void main() {
var bannedList = ['spam1', 'spam2', 'spam3'];
var bannedSet = bannedList.toSet();
print(bannedList.contains('spam3'));
print(bannedSet.contains('spam3'));
}true
trueSame answer; the set gets there without walking the whole collection.
Example 6 — Two Parallel Lists Into One Map
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']);
}{tea: 120, milk: 250, bread: 90}
250Example 7 — Student Marks: Map<String, List<int>>
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}');
}
}Ali: 80.0
Sara: 91.66666666666667Example 8 — Shopping Lines: List<Map<String, int>>
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');
}Total: 760Example 9 — Bank: Balances in a Map, Accounts Seen in a Set
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}');
}{Ali: 700, Sara: 400}
Active accounts: 2Example 10 — Temperature Readings per City
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}');
}{Lahore: [31, 34], Murree: [14]}
Lahore readings: 2Common Beginner Mistakes
1. Using an index on a set
void main() {
var guests = {'Ali', 'Sara'};
// print(guests[0]); // ✗ a set has no positions
}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
void main() {
var prices = {'tea': 120};
// int price = prices['tea']; // ✗ the key might be missing
}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
void main() {
var week = [
{'min': 12, 'max': 24},
];
// print(week[0]['max'] + 1); // ✗ ['max'] gives int?
}Error: Operator '+' cannot be called on 'int?' because it is potentially null.4. Collecting unique values in a list
void main() {
var visitors = <String>[];
for (final name in ['Ali', 'Sara', 'Ali']) {
visitors.add(name);
}
print('Unique visitors: ${visitors.length}');
}Unique visitors: 3No 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
?? defaultover!on map lookups unless the key is guaranteed. - Use
toSet()to remove duplicates andtoList()when you need sorting or indexes. - Use
putIfAbsent(key, () => [])to add to a group in aMap<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.
Different words: 3
{dart: 3, is: 2, fun: 1}Try it yourself before opening the solution.
Show solution
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);
}Different words: 3
{dart: 3, is: 2, fun: 1}Practice Questions
Beginner
- 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?
- Turn
[4, 2, 4, 9, 2]into a set and print it. - Create the map
{'Ali': 20}, set'Ali'to21, and print the map and its length. - Print the value for the missing key
'Zayan'in{'Ali': 20}, using?? 0.
Application
- Turn the set
{'mango', 'apple', 'kiwi'}into a sorted list and print it. - Build a map from
names = ['Ali', 'Sara']andages = [20, 18]and print it. - Given
[{'price': 50, 'qty': 4}, {'price': 30, 'qty': 2}], print the total cost. - Start with
{'A': ['Ali']}. Add'Sara'to group'A'and'Zayan'to a new group'B', then print the map.
Challenge
- Given
[85, 42, 85, 91, 42, 70], print the number of different marks and the highest one. - 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
void main() {
print([4, 2, 4, 9, 2].toSet());
}{4, 2, 9}Question 3 — Solution
void main() {
var ages = {'Ali': 20};
ages['Ali'] = 21;
print(ages);
print(ages.length);
}{Ali: 21}
1Question 4 — Solution
void main() {
var ages = {'Ali': 20};
print(ages['Zayan'] ?? 0);
}0Question 5 — Solution
void main() {
var fruits = {'mango', 'apple', 'kiwi'}.toList();
fruits.sort();
print(fruits);
}[apple, kiwi, mango]Question 6 — Solution
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);
}{Ali: 20, Sara: 18}Question 7 — Solution
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);
}260Question 8 — Solution
void main() {
Map<String, List<String>> groups = {
'A': ['Ali'],
};
groups['A']!.add('Sara');
groups.putIfAbsent('B', () => []).add('Zayan');
print(groups);
}{A: [Ali, Sara], B: [Zayan]}Question 9 — Solution
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}');
}Different marks: 4
Highest: 91After sorting, the highest mark is the last element.
Question 10 — Solution
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');
}{red: 3, blue: 2, green: 1}
Winner: red with 3 votesQuick Revision
- Which of the three collections gives you positions?
- What does a set do with a duplicate? What does a map do with an existing key?
- Why does
ages['Ali']have typeint?? - Why does
containson a long list get slower, and why doesn't it on a set? - How do you remove duplicates from a list?
- In
week[1]['max'], what does each pair of brackets look up? - What does
putIfAbsent(key, () => [])return? - 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.
containson 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.