Watch the video (12 min)
On this page26 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Transforming a List
  5. 2. forEach — Run a Function for Each Element
  6. 3. map — A New Value for Each Element
  7. 4. A Lazy Iterable and toList()
  8. 5. where — Keep Only the Matches
  9. 6. whereType — Keep One Type
  10. 7. expand — Many Values from Each Element
  11. 8. take and skip — By Count
  12. 9. takeWhile and skipWhile — By Condition
  13. 10. Chaining Transformations
  14. 11. sublist — Copy Part of a List
  15. 12. join — From a List to a String
  16. 13. split — From a String to a List
  17. 14. A Real-World Example
  18. Code Examples
  19. Common Beginner Mistakes
  20. Best Practices
  21. Mini Challenge
  22. Practice Questions
  23. Practice Answers
  24. Quick Revision
  25. Key Takeaways
  26. What's Next?

Lecture 43 — List Transformation

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

Doubling every price or keeping only the passing marks used to mean an empty list, a loop and an add. With map, where and their relatives, you describe the change in one line and Dart builds the new list for you.

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–042:

  • for-in loops and split('') on a string (Lecture 027)
  • arrow and anonymous functions (Lecture 034) and higher-order functions (Lecture 036)
  • creating lists, List<int>, and reading elements by index (Lectures 039–040)
  • add, removeWhere, and the RangeError (Lectures 040–041)
  • searching a list with a condition (Lecture 042)

Connection With Previous Lectures

To add 10% tax to every price, we can already write this:

dart
void main() {
  var prices = [100, 250, 400];
  var withTax = <double>[];
  for (final price in prices) {
    withTax.add(price * 1.1);
  }
  print(withTax);
}
output
[110.00000000000001, 275.0, 440.00000000000006]

Five lines of loop machinery for one idea: "multiply each price by 1.1". Keeping only the passing marks, or the first three names, needs the same machinery again. In Lecture 036 we saw that a higher-order function can hold the "how" while we pass in the "what". Lists come with exactly those functions built in.


Learning Objectives

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

  • Use forEach and explain why a for-in loop is usually preferred
  • Build new values from every element with map
  • Keep only matching elements with where and whereType
  • Explain why map and where return a lazy Iterable, and turn it into a list with toList()
  • Take or skip elements with take, skip, takeWhile, and skipWhile
  • Chain several transformations into one readable expression
  • Copy part of a list with sublist
  • Convert between lists and strings with join and split

1. Transforming a List

Transforming a list means producing a new sequence from it — changed values, fewer values, or both — while the original list stays exactly as it was.

Analogy — think of a photocopier with settings. You put in the original pages and choose "enlarge" or "only odd pages". You get a new stack; the original pages are untouched.

removeWhere from Lecture 041 changes a list. Every method in this lecture leaves it alone and gives you something new.


2. forEach — Run a Function for Each Element

You met forEach in Lecture 034. It calls your function once per element, in order:

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan'];
  names.forEach((name) => print('Hello, $name'));
  names.forEach(print);
}
output
Hello, Ali
Hello, Sara
Hello, Zayan
Ali
Sara
Zayan

The second call passes print as a tear-off (Lecture 035) — no parentheses. forEach returns nothing, so it is not really a transformation: it only does something with each element. For anything longer than one call, prefer a for-in loop; dart analyze suggests the same.


3. map — A New Value for Each Element

map calls your function on every element and collects what it returns:

dart
void main() {
  var marks = [85, 42, 78];
  var bonus = marks.map((mark) => mark + 5);
  print(bonus);
  print(marks);
}
output
(90, 47, 83)
[85, 42, 78]

Every mark got 5 more, and marks is unchanged. But look at the brackets: the result printed with round brackets, not square ones. It is not a list.


4. A Lazy Iterable and toList()

map returns an Iterable — the family that lists belong to: a sequence you can walk through with for-in. Round brackets in the output tell you that you are looking at an Iterable that is not a List. Call toList() to get a real list:

dart
void main() {
  var marks = [85, 42, 78];
  var bonus = marks.map((mark) => mark + 5).toList();
  print(bonus);
  print(bonus[0]);
}
output
[90, 47, 83]
90

Why not return a list straight away? Because the Iterable is lazy: it does no work until something asks for its values.

dart
void main() {
  var marks = [85, 42];
  var doubled = marks.map((mark) {
    print('Doubling $mark');
    return mark * 2;
  });
  print('Nothing doubled yet');
  print(doubled.toList());
}
output
Nothing doubled yet
Doubling 85
Doubling 42
[170, 84]

map only wrote down the recipe. The function ran when toList() asked for the values. If nothing ever asks, the work never happens. We study Iterable properly in Lecture 046 — Collection Features.


5. where — Keep Only the Matches

where takes a function that returns a bool and keeps every element for which it returns true:

dart
void main() {
  var marks = [85, 42, 78, 39, 91];
  var passed = marks.where((mark) => mark >= 50).toList();
  print(passed);
  print(marks);
}
output
[85, 78, 91]
[85, 42, 78, 39, 91]

It is the opposite of removeWhere: where keeps the matches, and it builds a new list instead of changing the old one.


6. whereType — Keep One Type

A List<Object> can hold different kinds of values (Lecture 039). whereType<T>() keeps only the elements of type T, and the result has that type:

dart
void main() {
  List<Object> form = ['Sara', 17, 'Lahore', 92.5, 3];
  List<int> wholeNumbers = form.whereType<int>().toList();
  print(form.whereType<String>());
  print(wholeNumbers);
}
output
(Sara, Lahore)
[17, 3]

7. expand — Many Values from Each Element

map gives exactly one value per element. expand lets your function return a list for each element, and joins all those lists into one sequence:

dart
void main() {
  var groups = [
    ['Ali', 'Sara'],
    ['Zayan'],
  ];
  print(groups.expand((group) => group).toList());
  print([1, 2, 3].expand((n) => [n, n * 10]).toList());
}
output
[Ali, Sara, Zayan]
[1, 10, 2, 20, 3, 30]

You will need expand less often than map and where; flattening a list of lists is its most common job.


8. take and skip — By Count

take(n) keeps the first n elements; skip(n) drops them and keeps the rest:

dart
void main() {
  var queue = ['Ali', 'Sara', 'Zayan', 'Hina', 'Omar'];
  print(queue.take(2).toList());
  print(queue.skip(2).toList());
  print(queue.take(10).toList());
}
output
[Ali, Sara]
[Zayan, Hina, Omar]
[Ali, Sara, Zayan, Hina, Omar]

Asking for more than there is is not an error — take(10) simply stops at the end.


9. takeWhile and skipWhile — By Condition

takeWhile keeps elements while the condition holds and stops at the first one that fails. skipWhile drops elements while it holds, then keeps everything after:

dart
void main() {
  var temps = [18, 21, 24, 31, 22, 35];
  print(temps.takeWhile((t) => t < 30).toList());
  print(temps.skipWhile((t) => t < 30).toList());
}
output
[18, 21, 24]
[31, 22, 35]

22 is below 30, but takeWhile had already stopped at 31. That is the difference from where, which checks every element.


10. Chaining Transformations

Each method returns an Iterable, so you can call the next method straight on the result. Put each step on its own line and read from top to bottom:

dart
void main() {
  var marks = [85, 42, 78, 39, 91, 66];
  var report = marks
      .where((mark) => mark >= 50)
      .map((mark) => 'Mark: $mark')
      .take(3)
      .toList();
  print(report);
}
output
[Mark: 85, Mark: 78, Mark: 91]

Keep the passing marks, turn each into a label, take the first three, make a list. toList() goes once, at the end of the chain.


11. sublist — Copy Part of a List

sublist(start, end) copies the elements from start up to, but not including, end — the same rule as removeRange in Lecture 041. Leave out end to copy to the end:

dart
void main() {
  var week = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
  print(week.sublist(0, 5));
  print(week.sublist(5));
}
output
[Mon, Tue, Wed, Thu, Fri]
[Sat, Sun]

Square brackets: sublist returns a real List straight away, no toList() needed. Unlike take, a wrong index crashes:

dart
void main() {
  print([1, 2, 3].sublist(1, 5)); // ✗ end is past the last index
}
output
Unhandled exception:
RangeError (end): Invalid value: Not in inclusive range 1..3: 5

12. join — From a List to a String

join glues the elements into one String, with the text you choose between them:

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan'];
  print(names.join(', '));
  print(names.join(' -> '));
  print([3, 1, 4].join());
}
output
Ali, Sara, Zayan
Ali -> Sara -> Zayan
314

With no argument, nothing goes between the elements. join works on any Iterable, so it is a handy last step of a chain instead of toList().


13. split — From a String to a List

In Lecture 027, split('') broke a string into characters. Give split any separator and it cuts the string wherever that separator appears, returning a List<String>:

dart
void main() {
  var line = 'Milk,Bread,Eggs';
  var items = line.split(',');
  print(items);
  print(items.length);
  print('Ali Sara Zayan'.split(' '));
}
output
[Milk, Bread, Eggs]
3
[Ali, Sara, Zayan]

split and join are opposites: split turns text into a list, join turns a list back into text.


14. A Real-World Example

Sara's teacher receives the marks as one line of text:

dart
void main() {
  var input = '85,42,78,39,91';
  var marks = input.split(',').map(int.parse).toList();
  var passed = marks.where((mark) => mark >= 50).toList();
  var curved = passed.map((mark) => mark + 5).toList();

  print('All marks: $marks');
  print('Passed: ${passed.length} of ${marks.length}');
  print('Curved: ${curved.join(' | ')}');
  print('Top two: ${curved.take(2).toList()}');
}
output
All marks: [85, 42, 78, 39, 91]
Passed: 3 of 5
Curved: 90 | 83 | 96
Top two: [90, 83]

split makes a List<String>, map(int.parse) turns each piece into an int (Lecture 010), where keeps the passes, map adds the curve, and join and take shape the report. The marks list never changes.


Code Examples

Example 1 — Greeting Every Guest With forEach

dart
void main() {
  var guests = ['Ali', 'Sara'];
  guests.forEach((guest) => print('Welcome, $guest!'));
}
output
Welcome, Ali!
Welcome, Sara!

Example 2 — Squares With map

dart
void main() {
  var numbers = [1, 2, 3, 4];
  var squares = numbers.map((n) => n * n).toList();
  print(squares);
}
output
[1, 4, 9, 16]

Example 3 — Names in Capitals

dart
void main() {
  var names = ['ali', 'sara', 'zayan'];
  print(names.map((name) => name.toUpperCase()).toList());
}
output
[ALI, SARA, ZAYAN]

Example 4 — Even Numbers With where

dart
void main() {
  var numbers = [3, 8, 12, 5, 20, 7];
  print(numbers.where((n) => n % 2 == 0).toList());
}
output
[8, 12, 20]

Example 5 — The Middle of a List

dart
void main() {
  var scores = [10, 20, 30, 40, 50];
  print(scores.skip(1).take(3).toList());
  print(scores.sublist(1, 4));
}
output
[20, 30, 40]
[20, 30, 40]

Two routes to the same result: skip/take count elements, sublist uses indexes.

Example 6 — Words to a Sentence and Back

dart
void main() {
  var sentence = 'Dart is fun';
  var words = sentence.split(' ');
  print(words);
  print(words.join('_'));
}
output
[Dart, is, fun]
Dart_is_fun

Example 7 — Student Marks: Grade Labels

dart
void main() {
  var marks = [92, 67, 45, 81];
  var grades = marks
      .map((mark) => mark >= 80 ? 'A' : (mark >= 50 ? 'B' : 'F'))
      .toList();
  print('Marks: $marks');
  print('Grades: ${grades.join(', ')}');
}
output
Marks: [92, 67, 45, 81]
Grades: A, B, F, A

How it works

  1. map turns each int into a String, so grades is a List<String>.
  2. The conditional expression from Lecture 023 picks the grade for each mark.

Example 8 — Shopping Total: Prices With Tax

dart
void main() {
  var prices = [200, 450, 1200];
  var withTax = prices.map((price) => price * 1.1).toList();
  var labels = withTax.map((p) => 'Rs ${p.toStringAsFixed(2)}');
  print(labels.join('\n'));
}
output
Rs 220.00
Rs 495.00
Rs 1320.00

How it works

  1. The first map makes a List<double>; the original prices stay as int.
  2. The second map formats each price with toStringAsFixed(2) (Lecture 009).
  3. join('\n') puts each label on its own line — no toList() needed.

Example 9 — Bank Balance: Deposits and Withdrawals

dart
void main() {
  var transactions = [500, -200, 1000, -50, 300];
  var deposits = transactions.where((t) => t > 0).toList();
  var withdrawals = transactions.where((t) => t < 0).map((t) => -t).toList();
  print('Deposits: $deposits');
  print('Withdrawals: $withdrawals');
}
output
Deposits: [500, 1000, 300]
Withdrawals: [200, 50]

How it works

  1. The first where keeps positive amounts.
  2. The second chain keeps negative amounts, then map flips their sign for display.
  3. Adding these up into a total is the topic of the next lecture.

Example 10 — Temperature: Celsius Readings to Fahrenheit

dart
void main() {
  var celsius = [21.0, -40.0, 30.0, 99.0];
  var fahrenheit = celsius
      .where((c) => c > -30 && c < 60)
      .map((c) => c * 9 / 5 + 32)
      .toList();
  print(fahrenheit);
}
output
[69.8, 86.0]

How it works

  1. where drops the two readings that look like sensor faults.
  2. map converts the rest with c * 9 / 5 + 32.
  3. toList() comes once, at the end of the chain.

Common Beginner Mistakes

1. Storing a map result in a List variable

dart
void main() {
  var marks = [85, 42, 78];
  // List<int> bonus = marks.map((m) => m + 5); // ✗
}
output
Error: A value of type 'Iterable<int>' can't be assigned to a variable of type 'List<int>'.

Add .toList() at the end of the chain.

2. Using an index on an Iterable

dart
void main() {
  var marks = [85, 42, 78];
  // print(marks.map((m) => m * 2)[0]); // ✗
}
output
Error: The operator '[]' isn't defined for the type 'Iterable<int>'.

Only a List has [index]. Call toList() first.

3. Expecting map to change the original list

dart
void main() {
  var marks = [85, 42, 78];
  marks.map((m) => m * 2); // result thrown away
  print(marks);
}
output
[85, 42, 78]

No error, no change — and because the Iterable is lazy, the function never even ran. Store the result: var doubled = marks.map((m) => m * 2).toList();.

4. Returning a non-bool from where

dart
void main() {
  var marks = [85, 42, 78];
  // marks.where((m) => m + 1); // ✗
}
output
Error: A value of type 'int' can't be returned from a function with return type 'bool'.

The function must answer yes or no: (m) => m > 50.

5. Using the result of forEach

dart
void main() {
  var marks = [85, 42, 78];
  // var result = marks.forEach((m) => m * 2);
  // print(result); // ✗
}
output
Error: This expression has type 'void' and can't be used.

forEach returns nothing. To collect new values, use map.

6. Confusing takeWhile with where

[18, 31, 22].takeWhile((t) => t < 30) gives [18], not [18, 22] — it stops at the first failure. Use where to check every element.


Best Practices

  • Use map to change values, where to choose them — and a for-in loop to simply do something with each element.
  • Call toList() once, at the end of a chain, and only when you need a real list.
  • Put each step of a long chain on its own line.
  • Keep the functions you pass short and free of side effects such as print.
  • Use sublist when you know indexes, take/skip when you know counts.
  • Finish with join when the goal is text.

Mini Challenge

Zayan's class list arrives as 'zayan,ali,sara,hina'. Split it, keep names with more than three letters, capitalize them, and print them joined with ' & '.

output
ZAYAN & SARA & HINA

Try it yourself before opening the solution.

Show solution
dart
void main() {
  var input = 'zayan,ali,sara,hina';
  var result = input
      .split(',')
      .where((name) => name.length > 3)
      .map((name) => name.toUpperCase())
      .join(' & ');
  print(result);
}
output
ZAYAN & SARA & HINA

Practice Questions

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

Beginner

  1. Use forEach to print Item: <name> for each of ['Pen', 'Book', 'Bag'].
  2. Use map to triple every number in [2, 4, 6] and print the result as a list.
  3. Use where to keep the numbers greater than 10 from [5, 15, 8, 22, 10] and print them as a list.
  4. Print [1, 2, 3].map((n) => n + 1) without toList(), then with it. What differs?

Application

  1. From ['Ali', 'Sara', 'Zayan', 'Hina', 'Omar'], print the first three names and then the rest, each as a list.
  2. From List<Object> data = [10, 'ten', 20, 'twenty', 30], print only the String values as a list.
  3. Split 'red;green;blue' into a list, print it, then join it back with ' / '.
  4. From [45, 67, 89, 30, 72], keep marks of at least 50, add 3 to each, and print the result as a list — in one chain.

Challenge

  1. Temperatures arrive as '19,23,28,33,26,36'. Print the readings before the first one above 30, then the readings from that one onwards.
  2. From [1200, 300, 4500, 800, 150], keep prices over 500, apply a 10% discount, and print them as Rs 1080.0, Rs 4050.0, Rs 720.0.

Practice Answers

Question 1 — Solution

dart
void main() {
  ['Pen', 'Book', 'Bag'].forEach((item) => print('Item: $item'));
}
output
Item: Pen
Item: Book
Item: Bag

Question 2 — Solution

dart
void main() {
  print([2, 4, 6].map((n) => n * 3).toList());
}
output
[6, 12, 18]

Question 3 — Solution

dart
void main() {
  print([5, 15, 8, 22, 10].where((n) => n > 10).toList());
}
output
[15, 22]

10 is not greater than 10, so it is left out.

Question 4 — Solution

dart
void main() {
  print([1, 2, 3].map((n) => n + 1));
  print([1, 2, 3].map((n) => n + 1).toList());
}
output
(2, 3, 4)
[2, 3, 4]

Round brackets mean a lazy Iterable; square brackets mean a List.

Question 5 — Solution

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan', 'Hina', 'Omar'];
  print(names.take(3).toList());
  print(names.skip(3).toList());
}
output
[Ali, Sara, Zayan]
[Hina, Omar]

Question 6 — Solution

dart
void main() {
  List<Object> data = [10, 'ten', 20, 'twenty', 30];
  print(data.whereType<String>().toList());
}
output
[ten, twenty]

Question 7 — Solution

dart
void main() {
  var colors = 'red;green;blue'.split(';');
  print(colors);
  print(colors.join(' / '));
}
output
[red, green, blue]
red / green / blue

Question 8 — Solution

dart
void main() {
  var marks = [45, 67, 89, 30, 72];
  var result = marks.where((m) => m >= 50).map((m) => m + 3).toList();
  print(result);
}
output
[70, 92, 75]

Question 9 — Solution

dart
void main() {
  var temps = '19,23,28,33,26,36'.split(',').map(int.parse).toList();
  print(temps.takeWhile((t) => t <= 30).toList());
  print(temps.skipWhile((t) => t <= 30).toList());
}
output
[19, 23, 28]
[33, 26, 36]

26 appears in the second list: skipWhile stops skipping at 33 and keeps everything after.

Question 10 — Solution

dart
void main() {
  var prices = [1200, 300, 4500, 800, 150];
  var result = prices
      .where((p) => p > 500)
      .map((p) => p * 0.9)
      .map((p) => 'Rs $p')
      .join(', ');
  print(result);
}
output
Rs 1080.0, Rs 4050.0, Rs 720.0

Quick Revision

  1. What does forEach return?
  2. What is the difference between map and where?
  3. What do round brackets in printed output tell you?
  4. What does "lazy" mean for the result of map?
  5. How is takeWhile different from where?
  6. Does sublist need toList()? Why or why not?
  7. Which method turns a list into a String, and which turns a String into a list?

Key Takeaways

  • Transformation methods build something new; the original list never changes.
  • forEach runs a function for each element and returns nothing — prefer for-in.
  • map produces one new value per element; where keeps the elements that pass a test.
  • whereType<T>() keeps only elements of type T; expand turns each element into many.
  • take/skip work by count; takeWhile/skipWhile stop at the first element that fails.
  • map, where and friends return a lazy Iterable, printed with ( ); toList() makes a real List.
  • Chain steps one per line, with toList() or join once at the end.
  • sublist(start, end) returns a List copy and throws a RangeError for bad indexes.
  • join turns a list into a String; split turns a String into a List<String>.

What's Next?

Lecture 44 — List Aggregation. Every method here turned a list into another sequence. Often we want just one answer instead — the total of a shopping cart, the highest mark, the balance after all transactions. Next we combine a whole list into a single value with reduce, fold, and their relatives.


Video lecture

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