Watch the video (12 min)
On this page24 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. The Spread Operator ...
  5. 2. Spread Builds a New List
  6. 3. Null-Aware Spread ...?
  7. 4. Collection if
  8. 5. Collection for
  9. 6. for and if Together
  10. 7. Null-Aware Elements ?value
  11. 8. List.generate
  12. 9. const Lists
  13. 10. List.unmodifiable
  14. 11. Iterable vs List
  15. 12. A Real-World Example
  16. Code Examples
  17. Common Beginner Mistakes
  18. Best Practices
  19. Mini Challenge
  20. Practice Questions
  21. Practice Answers
  22. Quick Revision
  23. Key Takeaways
  24. What's Next?

Lecture 46 — Collection Features

Category7 — Lists
Lecture046 of 140
Estimated video length12 min
TargetDart 3.12.2
LevelBeginner

Until now a list was built first and fixed up afterwards with add, if and loops. Spread, collection if and collection for let you write the finished list in one literal — and const and List.unmodifiable make 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 isMember are declared above main(), to imitate values that come from outside the program. Inside main(), 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:

  • final and const (Lecture 006) and nullable types (Lecture 013)
  • null-aware operators such as ?? and ?. (Lecture 014)
  • if statements (Lecture 022), for loops (Lecture 026) and for-in loops (Lecture 027)
  • arrow functions (Lecture 034)
  • creating lists, indexes, add, addAll and List.filled (Lectures 039–041)
  • where, map and toList (Lecture 043)

Connection With Previous Lectures

So far, building a list has taken several statements:

dart
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 null with ...?
  • Include an element only when a condition is true, using collection if
  • Produce many elements from a loop, using collection for
  • Skip null values with null-aware elements: ?value
  • Create lists with List.generate and List.filled
  • Choose between final, const and List.unmodifiable lists
  • Recognize an Iterable and turn it into a List

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.

dart
void main() {
  var fruits = ['apple', 'mango'];
  var basket = ['bread', ...fruits, 'milk'];
  print(basket);
}
output
[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.

dart
void main() {
  var morning = ['Ali', 'Sara'];
  var evening = ['Zayan'];
  var everyone = [...morning, ...evening];
  everyone.add('Hina');
  print(everyone);
  print(morning);
}
output
[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.

dart
List<int>? bonus;

void main() {
  var marks = [85, 92, ...?bonus];
  print(marks);

  bonus = [5, 10];
  print([85, 92, ...?bonus]);
}
output
[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.

dart
var isMember = true;
var hasCoupon = false;

void main() {
  var cart = [
    'Milk',
    if (isMember) 'Free gift',
    if (hasCoupon) 'Discount' else 'Full price',
  ];
  print(cart);
}
output
[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.

dart
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);
}
output
[#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.

dart
void main() {
  var marks = [72, 45, 88, 30, 65];
  var passed = [
    for (final mark in marks)
      if (mark >= 50) mark,
  ];
  print(passed);
}
output
[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.

dart
String? middleName;
String? nickname = 'Zee';

void main() {
  var parts = ['Zayan', ?middleName, ?nickname];
  print(parts);
}
output
[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.

dart
void main() {
  var evens = List.generate(5, (i) => i * 2);
  var seats = List.generate(3, (i) => 'Seat ${i + 1}');
  print(evens);
  print(seats);
}
output
[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.

dart
void main() {
  const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
  final grades = const ['A', 'B', 'C'];
  print(weekdays.length);
  print(grades);
}
output
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.

dart
void main() {
  var entered = [85, 92];
  entered.add(78);
  final locked = List<int>.unmodifiable(entered);
  entered.add(10);
  print(locked);
  print(entered);
}
output
[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.

dart
void main() {
  var marks = [85, 42, 92];
  var high = marks.where((m) => m > 80);
  print(high);
  var highList = high.toList();
  print(highList[0]);
}
output
(85, 92)
85

Round 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

dart
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}');
}
output
[Rice, Milk, Tea, Free gift, Item 300, Item 450]
Items: 6

The 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

dart
void main() {
  var a = [1, 2];
  var b = [3];
  var c = [4, 5];
  print([...a, ...b, ...c]);
}
output
[1, 2, 3, 4, 5]

Example 2 — A Quick Copy

dart
void main() {
  var original = ['red', 'green'];
  var copy = [...original];
  copy.add('blue');
  print(original);
  print(copy);
}
output
[red, green]
[red, green, blue]

Example 3 — Optional Extras

dart
List<String>? extras;

void main() {
  print(['Burger', ...?extras]);
  extras = ['Fries', 'Juice'];
  print(['Burger', ...?extras]);
}
output
[Burger]
[Burger, Fries, Juice]

Example 4 — A Menu That Depends on Login

dart
var loggedIn = false;

void main() {
  var menu = ['Home', if (loggedIn) 'Profile' else 'Log in', 'Help'];
  print(menu);
}
output
[Home, Log in, Help]

Example 5 — A Times Table

dart
void main() {
  var table = [for (var i = 1; i <= 5; i++) '3 x $i = ${3 * i}'];
  for (final line in table) {
    print(line);
  }
}
output
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
3 x 4 = 12
3 x 5 = 15

Example 6 — Generated Labels

dart
void main() {
  var rooms = List.generate(4, (i) => 'Room ${101 + i}');
  print(rooms);
}
output
[Room 101, Room 102, Room 103, Room 104]

Example 7 — Student Marks: Passing Names

dart
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);
}
output
[Sara (88), Zayan (72)]

The index i reads the matching name and mark from the two lists.

Example 8 — Shopping Total With Optional Delivery

dart
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');
}
output
[450, 1200, 300, 150]
Total: 2100

discount is null, so it never enters the list and the loop never has to check for it.

Example 9 — Bank Balance: Locked Statement

dart
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');
}
output
Statement: [500, -200, 1000]
Balance: 1300

The statement is a copy taken at one moment; the later -50 does not reach it.

Example 10 — Temperature: Celsius to Fahrenheit

dart
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);
  }
}
output
18.0°C is cool
25.0°C = 77.0°F
31.0°C = 87.8°F

A for-in loop can read a const list; only changing it is forbidden.


Common Beginner Mistakes

1. Spreading a nullable list with plain ...

dart
void main() {
  List<int>? extra;
  var marks = [85, ...extra]; // ✗ extra may be null
  print(marks);
}
output
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

dart
void main() {
  var score = 90;
  const marks = [85, score]; // ✗ score is not constant
  print(marks);
}
output
Error: Not a constant expression.

Use final marks = [85, score];, or make score a const too.

3. Adding braces to collection if

dart
void main() {
  var isMember = true;
  List<String> items = [
    'Milk',
    if (isMember) {'Gift'}, // ✗ braces
  ];
  print(items);
}
output
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

dart
void main() {
  var marks = [85, 92, 78];
  var high = marks.where((m) => m > 80);
  print(high[0]); // ✗ no index on Iterable
}
output
Error: The operator '[]' isn't defined for the type 'Iterable<int>'.

Call toList() first: high.toList()[0].

5. Filling slots with one shared list

dart
void main() {
  var rows = List.filled(2, <int>[]);
  rows[0].add(5);
  print(rows);
}
output
[[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, if and for instead of add after add.
  • Use ...? and ?value instead of checking for null by hand.
  • Use const for lists known while writing the code; List.unmodifiable for locked copies made at run time; final for lists that will still change.
  • Give List.unmodifiable its type: List<int>.unmodifiable(...).
  • Use List.generate, not List.filled, when each slot needs its own new list.
  • Call toList() when you need indexes or a list you can change.
  • Let dart format lay 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.

output
[Math, English, Art, Physics]

Try it yourself before opening the solution.

Show solution
dart
String? optional = 'Art';
var age = 16;

void main() {
  const core = ['Math', 'English'];
  var subjects = [...core, ?optional, if (age > 15) 'Physics'];
  print(subjects);
}
output
[Math, English, Art, Physics]

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Join [1, 2] and [3, 4] into one new list with spread and print it.
  2. Create List<String>? extra; and print ['a', ...?extra].
  3. Use List.generate to print the list [1, 2, 3, 4, 5].

Application

  1. With var isRaining = true;, build ['Shoes', 'Umbrella'] when it rains and ['Shoes', 'Sunglasses'] when it doesn't, using one literal with if/else.
  2. Use collection for to build ['Hello Ali', 'Hello Sara', 'Hello Zayan'] from ['Ali', 'Sara', 'Zayan'].
  3. From [3, 8, 12, 5, 20], build a list of only the numbers greater than 6, using for and if inside a literal.
  4. With String? phone; and String? email = 'sara@mail.com';, build a contact list that starts with 'Sara' and includes only the details that exist.
  5. Make an unmodifiable copy of [10, 20], add 30 to the original, and print both.

Challenge

  1. With final prices = [450, 1200, 300]; and var hasDelivery = true;, build in one literal a list of each price with 17% tax added (price * 1.17), followed by a delivery fee of 150.0 only if hasDelivery is true. Print the list.
  2. Use List.generate to build the temperatures [20, 22, 24, 26, 28]. Then use collection for and if to build labels such as '20°C', keeping only temperatures below 27. Print both lists.

Practice Answers

Question 1 — Solution

dart
void main() {
  var a = [1, 2];
  var b = [3, 4];
  print([...a, ...b]);
}
output
[1, 2, 3, 4]

Question 2 — Solution

dart
void main() {
  List<String>? extra;
  print(['a', ...?extra]);
}
output
[a]

Question 3 — Solution

dart
void main() {
  print(List.generate(5, (i) => i + 1));
}
output
[1, 2, 3, 4, 5]

Question 4 — Solution

dart
var isRaining = true;

void main() {
  var bag = ['Shoes', if (isRaining) 'Umbrella' else 'Sunglasses'];
  print(bag);
}
output
[Shoes, Umbrella]

Question 5 — Solution

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan'];
  print([for (final name in names) 'Hello $name']);
}
output
[Hello Ali, Hello Sara, Hello Zayan]

Question 6 — Solution

dart
void main() {
  var numbers = [3, 8, 12, 5, 20];
  var big = [
    for (final n in numbers)
      if (n > 6) n,
  ];
  print(big);
}
output
[8, 12, 20]

Question 7 — Solution

dart
String? phone;
String? email = 'sara@mail.com';

void main() {
  var contact = ['Sara', ?phone, ?email];
  print(contact);
}
output
[Sara, sara@mail.com]

Question 8 — Solution

dart
void main() {
  var original = [10, 20];
  final copy = List<int>.unmodifiable(original);
  original.add(30);
  print(original);
  print(copy);
}
output
[10, 20, 30]
[10, 20]

Question 9 — Solution

dart
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);
}
output
[526.5, 1404.0, 351.0, 150.0]

Question 10 — Solution

dart
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);
}
output
[20, 22, 24, 26, 28]
[20°C, 22°C, 24°C, 26°C]

Quick Revision

  1. What does ... do inside a list literal?
  2. When does ...? add nothing?
  3. Why does collection if have no braces?
  4. What does collection for add on each iteration?
  5. What does ?value do when value is null?
  6. How is List.generate different from List.filled?
  7. When must you use List.unmodifiable instead of const?
  8. Why can't you write iterable[0], and how do you fix it?

Key Takeaways

  • ...list spreads a list's elements into a new list; ...?list skips a null list.
  • Collection if adds an element only when its condition is true; else picks the alternative.
  • Collection for adds one element per iteration and can contain an if.
  • ?value adds one element only if it is not null (Dart 3.8 and later).
  • List.generate(n, (i) => …) builds each element from its index; List.filled repeats one value.
  • final locks the variable; const locks the list and needs constant values.
  • List<T>.unmodifiable(…) makes a locked copy from values known only at run time.
  • where and map return an Iterable: walkable with for-in, but no index — use toList().

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.


Video lecture

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