On this page24 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. The Spread Operator ...
- 2. Spread Builds a New List
- 3. Null-Aware Spread ...?
- 4. Collection if
- 5. Collection for
- 6. for and if Together
- 7. Null-Aware Elements ?value
- 8. List.generate
- 9. const Lists
- 10. List.unmodifiable
- 11. Iterable vs List
- 12. 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 46 — Collection Features
| Category | 7 — Lists |
| Lecture | 046 of 140 |
| Estimated video length | 12 min |
| Target | Dart 3.12.2 |
| Level | Beginner |
Until now a list was built first and fixed up afterwards with
add,ifand loops. Spread, collectionifand collectionforlet you write the finished list in one literal — andconstandList.unmodifiablemake sure nobody changes it later.
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.
A note on the examples. As in Lecture 014, nullable values and on/off switches such as
isMemberare declared abovemain(), to imitate values that come from outside the program. Insidemain(), Dart can see the value you just stored and would warn that the check is unnecessary.
Prerequisites
Students should already understand, from Lectures 001–045:
finalandconst(Lecture 006) and nullable types (Lecture 013)- null-aware operators such as
??and?.(Lecture 014) ifstatements (Lecture 022),forloops (Lecture 026) andfor-inloops (Lecture 027)- arrow functions (Lecture 034)
- creating lists, indexes,
add,addAllandList.filled(Lectures 039–041) where,mapandtoList(Lecture 043)
Connection With Previous Lectures
So far, building a list has taken several statements:
final cart = ['Milk', 'Bread'];
if (isMember) {
cart.add('Free gift');
}
cart.addAll(extras);The list is only correct after the last line runs. And in Lecture 041 we saw that a
const list refuses add — so a list we want to protect cannot be built this way at all.
Dart lets us describe the whole list, conditions and loops included, inside the square
brackets.
Learning Objectives
By the end of this lecture you will be able to:
- Copy one list into another with the spread operator
... - Spread a list that may be
nullwith...? - Include an element only when a condition is true, using collection
if - Produce many elements from a loop, using collection
for - Skip
nullvalues with null-aware elements:?value - Create lists with
List.generateandList.filled - Choose between
final,constandList.unmodifiablelists - Recognize an
Iterableand turn it into aList
1. The Spread Operator ...
Three dots in front of a list unpack its elements into the list you are writing.
Analogy — emptying a bag of groceries onto the counter. You don't put the bag on the counter; you put out what is inside it.
void main() {
var fruits = ['apple', 'mango'];
var basket = ['bread', ...fruits, 'milk'];
print(basket);
}[bread, apple, mango, milk]Without the dots, the whole list becomes one element: ['bread', fruits] prints
[bread, [apple, mango]].
2. Spread Builds a New List
Spreading copies the elements. The new list and the old one are independent.
void main() {
var morning = ['Ali', 'Sara'];
var evening = ['Zayan'];
var everyone = [...morning, ...evening];
everyone.add('Hina');
print(everyone);
print(morning);
}[Ali, Sara, Zayan, Hina]
[Ali, Sara]addAll from Lecture 041 changes an existing list; spread creates a new one.
3. Null-Aware Spread ...?
A list variable of type List<int>? may be null. Plain ... refuses it; ...? spreads it
when it exists and adds nothing when it is null.
List<int>? bonus;
void main() {
var marks = [85, 92, ...?bonus];
print(marks);
bonus = [5, 10];
print([85, 92, ...?bonus]);
}[85, 92]
[85, 92, 5, 10]It is the same "if it's null, skip it" idea as ?. in Lecture 014.
4. Collection if
An if written inside the brackets adds its element only when the condition is true. It
has no braces and no semicolon.
var isMember = true;
var hasCoupon = false;
void main() {
var cart = [
'Milk',
if (isMember) 'Free gift',
if (hasCoupon) 'Discount' else 'Full price',
];
print(cart);
}[Milk, Free gift, Full price]With else, one of the two elements is always added. Combine if with spread to add several
elements at once: if (isMember) ...['Gift', 'Card'].
5. Collection for
A for inside the brackets produces one element per iteration. Both loop styles from
Lectures 026–027 work.
void main() {
var names = ['ali', 'sara'];
var tags = [for (final name in names) '#$name'];
var squares = [for (var i = 1; i <= 4; i++) i * i];
print(tags);
print(squares);
}[#ali, #sara]
[1, 4, 9, 16]The value after the for (…) part is the element added each time — no add needed.
6. for and if Together
Collection if can sit inside collection for, so each element is checked before it is
added.
void main() {
var marks = [72, 45, 88, 30, 65];
var passed = [
for (final mark in marks)
if (mark >= 50) mark,
];
print(passed);
}[72, 88, 65]Read it aloud: "for each mark, if it is at least 50, the mark."
7. Null-Aware Elements ?value
A ? in front of a single element adds it only if it is not null. This feature arrived in
Dart 3.8.
String? middleName;
String? nickname = 'Zee';
void main() {
var parts = ['Zayan', ?middleName, ?nickname];
print(parts);
}[Zayan, Zee]The list's type is List<String>, not List<String?> — the nulls never get in. Compare
the three null-skipping tools: ?. for a method call, ...? for a whole list, ?value for
one element.
8. List.generate
List.generate(length, function) calls the function once for each index — 0, 1, 2, … —
and stores what it returns.
void main() {
var evens = List.generate(5, (i) => i * 2);
var seats = List.generate(3, (i) => 'Seat ${i + 1}');
print(evens);
print(seats);
}[0, 2, 4, 6, 8]
[Seat 1, Seat 2, Seat 3]List.generate lists are growable. List.filled(length, value) from Lecture 041 puts the
same value in every slot and is fixed-length unless you pass growable: true.
9. const Lists
const before a list literal makes the whole list a compile-time constant: no element can be
added, removed or replaced. Every element must itself be a constant.
void main() {
const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
final grades = const ['A', 'B', 'C'];
print(weekdays.length);
print(grades);
}5
[A, B, C]final grades = const [...] shows the difference clearly: final locks the variable,
const locks the list. Collection if, for and spread also work inside a const list,
as long as everything they use is constant.
10. List.unmodifiable
const needs values known while writing the code. When the values come from the running
program, List.unmodifiable makes a locked copy.
void main() {
var entered = [85, 92];
entered.add(78);
final locked = List<int>.unmodifiable(entered);
entered.add(10);
print(locked);
print(entered);
}[85, 92, 78]
[85, 92, 78, 10]Calling locked.add(70) compiles, then stops the program with
Unsupported operation: Cannot add to an unmodifiable list — the same message a const list
gives. Write the type, List<int>.unmodifiable; without it the copy is a List<dynamic>.
11. Iterable vs List
Iterable is the family that lists belong to: anything you can walk through with for-in.
Methods like where and map give back an Iterable, not a List. An Iterable has no
index, so high[0] does not work; call toList() when you need a real list.
void main() {
var marks = [85, 42, 92];
var high = marks.where((m) => m > 80);
print(high);
var highList = high.toList();
print(highList[0]);
}(85, 92)
85Round brackets in the output mean an Iterable; square brackets mean a List. We will meet
Iterable again with Sets (Lecture 047) and Maps (Lecture 049).
12. A Real-World Example
List<String>? savedItems = ['Tea'];
String? note;
var isMember = true;
void main() {
const fixedItems = ['Rice', 'Milk'];
var prices = [300, 150, 450];
final cart = List<String>.unmodifiable([
...fixedItems,
...?savedItems,
?note,
if (isMember) 'Free gift',
for (final price in prices)
if (price > 200) 'Item $price',
]);
print(cart);
print('Items: ${cart.length}');
}[Rice, Milk, Tea, Free gift, Item 300, Item 450]
Items: 6The cart is described in one literal: two spreads, a skipped null note, a member gift, and
one entry per price over 200. List.unmodifiable then locks the result.
Code Examples
Example 1 — Joining Three Lists
void main() {
var a = [1, 2];
var b = [3];
var c = [4, 5];
print([...a, ...b, ...c]);
}[1, 2, 3, 4, 5]Example 2 — A Quick Copy
void main() {
var original = ['red', 'green'];
var copy = [...original];
copy.add('blue');
print(original);
print(copy);
}[red, green]
[red, green, blue]Example 3 — Optional Extras
List<String>? extras;
void main() {
print(['Burger', ...?extras]);
extras = ['Fries', 'Juice'];
print(['Burger', ...?extras]);
}[Burger]
[Burger, Fries, Juice]Example 4 — A Menu That Depends on Login
var loggedIn = false;
void main() {
var menu = ['Home', if (loggedIn) 'Profile' else 'Log in', 'Help'];
print(menu);
}[Home, Log in, Help]Example 5 — A Times Table
void main() {
var table = [for (var i = 1; i <= 5; i++) '3 x $i = ${3 * i}'];
for (final line in table) {
print(line);
}
}3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
3 x 4 = 12
3 x 5 = 15Example 6 — Generated Labels
void main() {
var rooms = List.generate(4, (i) => 'Room ${101 + i}');
print(rooms);
}[Room 101, Room 102, Room 103, Room 104]Example 7 — Student Marks: Passing Names
void main() {
var names = ['Ali', 'Sara', 'Zayan'];
var marks = [45, 88, 72];
var passed = [
for (var i = 0; i < names.length; i++)
if (marks[i] >= 50) '${names[i]} (${marks[i]})',
];
print(passed);
}[Sara (88), Zayan (72)]The index i reads the matching name and mark from the two lists.
Example 8 — Shopping Total With Optional Delivery
int? deliveryFee = 150;
int? discount;
void main() {
var items = [450, 1200, 300];
var lines = [...items, ?deliveryFee, ?discount];
var total = 0;
for (final amount in lines) {
total += amount;
}
print(lines);
print('Total: $total');
}[450, 1200, 300, 150]
Total: 2100discount is null, so it never enters the list and the loop never has to check for it.
Example 9 — Bank Balance: Locked Statement
void main() {
var transactions = [500, -200, 1000];
final statement = List<int>.unmodifiable(transactions);
transactions.add(-50);
var balance = 0;
for (final amount in statement) {
balance += amount;
}
print('Statement: $statement');
print('Balance: $balance');
}Statement: [500, -200, 1000]
Balance: 1300The statement is a copy taken at one moment; the later -50 does not reach it.
Example 10 — Temperature: Celsius to Fahrenheit
void main() {
const celsius = [18.0, 25.0, 31.0];
var report = [
for (final c in celsius)
if (c > 20) '$c°C = ${c * 9 / 5 + 32}°F' else '$c°C is cool',
];
for (final line in report) {
print(line);
}
}18.0°C is cool
25.0°C = 77.0°F
31.0°C = 87.8°FA for-in loop can read a const list; only changing it is forbidden.
Common Beginner Mistakes
1. Spreading a nullable list with plain ...
void main() {
List<int>? extra;
var marks = [85, ...extra]; // ✗ extra may be null
print(marks);
}Error: An expression whose value can be 'null' must be null-checked before it can be dereferenced.Use ...?extra.
2. Putting a variable in a const list
void main() {
var score = 90;
const marks = [85, score]; // ✗ score is not constant
print(marks);
}Error: Not a constant expression.Use final marks = [85, score];, or make score a const too.
3. Adding braces to collection if
void main() {
var isMember = true;
List<String> items = [
'Milk',
if (isMember) {'Gift'}, // ✗ braces
];
print(items);
}Error: A value of type 'Set<String>' can't be assigned to a variable of type 'String'.Inside a list, {'Gift'} is not a block — it is a Set literal, which we meet in Lecture
047. Write if (isMember) 'Gift'.
4. Indexing an Iterable
void main() {
var marks = [85, 92, 78];
var high = marks.where((m) => m > 80);
print(high[0]); // ✗ no index on Iterable
}Error: The operator '[]' isn't defined for the type 'Iterable<int>'.Call toList() first: high.toList()[0].
5. Filling slots with one shared list
void main() {
var rows = List.filled(2, <int>[]);
rows[0].add(5);
print(rows);
}[[5], [5]]List.filled puts the same list in every slot, so changing one changes "all". Use
List.generate(2, (i) => <int>[]) to get a fresh list per slot.
6. A ? on a value that can't be null
var name = 'Ali'; [?name] runs, but dart analyze warns: The element can't be null, so the
null-aware operator '?' is unnecessary. Use ? only on nullable values.
Best Practices
- Build a list in one literal with spread,
ifandforinstead ofaddafteradd. - Use
...?and?valueinstead of checking fornullby hand. - Use
constfor lists known while writing the code;List.unmodifiablefor locked copies made at run time;finalfor lists that will still change. - Give
List.unmodifiableits type:List<int>.unmodifiable(...). - Use
List.generate, notList.filled, when each slot needs its own new list. - Call
toList()when you need indexes or a list you can change. - Let
dart formatlay out long literals — one element per line keeps them readable.
Mini Challenge
Ali has ['Math', 'English'] as core subjects and may pick an optional subject
(String? optional = 'Art';). Students over 15 also get 'Physics'. Build the subject list in
one literal for a student aged 16, then print it.
[Math, English, Art, Physics]Try it yourself before opening the solution.
Show solution
String? optional = 'Art';
var age = 16;
void main() {
const core = ['Math', 'English'];
var subjects = [...core, ?optional, if (age > 15) 'Physics'];
print(subjects);
}[Math, English, Art, Physics]Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Join
[1, 2]and[3, 4]into one new list with spread and print it. - Create
List<String>? extra;and print['a', ...?extra]. - Use
List.generateto print the list[1, 2, 3, 4, 5].
Application
- With
var isRaining = true;, build['Shoes', 'Umbrella']when it rains and['Shoes', 'Sunglasses']when it doesn't, using one literal withif/else. - Use collection
forto build['Hello Ali', 'Hello Sara', 'Hello Zayan']from['Ali', 'Sara', 'Zayan']. - From
[3, 8, 12, 5, 20], build a list of only the numbers greater than6, usingforandifinside a literal. - With
String? phone;andString? email = 'sara@mail.com';, build a contact list that starts with'Sara'and includes only the details that exist. - Make an unmodifiable copy of
[10, 20], add30to the original, and print both.
Challenge
- With
final prices = [450, 1200, 300];andvar hasDelivery = true;, build in one literal a list of each price with 17% tax added (price * 1.17), followed by a delivery fee of150.0only ifhasDeliveryis true. Print the list. - Use
List.generateto build the temperatures[20, 22, 24, 26, 28]. Then use collectionforandifto build labels such as'20°C', keeping only temperatures below27. Print both lists.
Practice Answers
Question 1 — Solution
void main() {
var a = [1, 2];
var b = [3, 4];
print([...a, ...b]);
}[1, 2, 3, 4]Question 2 — Solution
void main() {
List<String>? extra;
print(['a', ...?extra]);
}[a]Question 3 — Solution
void main() {
print(List.generate(5, (i) => i + 1));
}[1, 2, 3, 4, 5]Question 4 — Solution
var isRaining = true;
void main() {
var bag = ['Shoes', if (isRaining) 'Umbrella' else 'Sunglasses'];
print(bag);
}[Shoes, Umbrella]Question 5 — Solution
void main() {
var names = ['Ali', 'Sara', 'Zayan'];
print([for (final name in names) 'Hello $name']);
}[Hello Ali, Hello Sara, Hello Zayan]Question 6 — Solution
void main() {
var numbers = [3, 8, 12, 5, 20];
var big = [
for (final n in numbers)
if (n > 6) n,
];
print(big);
}[8, 12, 20]Question 7 — Solution
String? phone;
String? email = 'sara@mail.com';
void main() {
var contact = ['Sara', ?phone, ?email];
print(contact);
}[Sara, sara@mail.com]Question 8 — Solution
void main() {
var original = [10, 20];
final copy = List<int>.unmodifiable(original);
original.add(30);
print(original);
print(copy);
}[10, 20, 30]
[10, 20]Question 9 — Solution
void main() {
final prices = [450, 1200, 300];
var hasDelivery = true;
var bill = [for (final price in prices) price * 1.17, if (hasDelivery) 150.0];
print(bill);
}[526.5, 1404.0, 351.0, 150.0]Question 10 — Solution
void main() {
var temps = List.generate(5, (i) => 20 + i * 2);
var labels = [
for (final t in temps)
if (t < 27) '$t°C',
];
print(temps);
print(labels);
}[20, 22, 24, 26, 28]
[20°C, 22°C, 24°C, 26°C]Quick Revision
- What does
...do inside a list literal? - When does
...?add nothing? - Why does collection
ifhave no braces? - What does collection
foradd on each iteration? - What does
?valuedo whenvalueisnull? - How is
List.generatedifferent fromList.filled? - When must you use
List.unmodifiableinstead ofconst? - Why can't you write
iterable[0], and how do you fix it?
Key Takeaways
...listspreads a list's elements into a new list;...?listskips anulllist.- Collection
ifadds an element only when its condition is true;elsepicks the alternative. - Collection
foradds one element per iteration and can contain anif. ?valueadds one element only if it is notnull(Dart 3.8 and later).List.generate(n, (i) => …)builds each element from its index;List.filledrepeats one value.finallocks the variable;constlocks the list and needs constant values.List<T>.unmodifiable(…)makes a locked copy from values known only at run time.whereandmapreturn anIterable: walkable withfor-in, but no index — usetoList().
What's Next?
Lecture 47 — Sets. This lecture closes Category 7 — Lists. A list keeps order and allows
duplicates; but sometimes duplicates are exactly what you want to prevent — the students who
attended today, the tags on a post. A Set stores each value only once, and everything you
learned here — spread, collection if and for — works in sets too.