Watch the video (10 min)
On this page25 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Many Values, One Answer
  5. 2. Counting With where(...).length
  6. 3. reduce — Combine Two at a Time
  7. 4. reduce on an Empty List
  8. 5. fold — Start From an Initial Value
  9. 6. fold Can Return a Different Type
  10. 7. The Initial Value Sets the Type
  11. 8. The Average
  12. 9. Highest and Lowest by Hand
  13. 10. Highest and Lowest With reduce
  14. 11. No .sum or .max Built In
  15. 12. map and fold Together
  16. 13. A Real-World Example
  17. Code Examples
  18. Common Beginner Mistakes
  19. Best Practices
  20. Mini Challenge
  21. Practice Questions
  22. Practice Answers
  23. Quick Revision
  24. Key Takeaways
  25. What's Next?

Lecture 44 — List Aggregation

Category7 — Lists
Lecture044 of 140
Estimated video length10 min
TargetDart 3.12.2
LevelBeginner

Lecture 043 turned a list into another list. Aggregation turns a whole list into one answer — a total, a count, an average, a highest or lowest value.

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

  • toStringAsFixed() (Lecture 009) and the conditional operator ? : (Lecture 020)
  • for-in loops (Lecture 027)
  • arrow and anonymous functions (Lecture 034) and higher-order functions (Lecture 036)
  • length, isEmpty, first, and Bad state: No element (Lectures 039–040)
  • where and map, and the lazy Iterable they return (Lecture 043)

Connection With Previous Lectures

In Lecture 043, map and where gave us new collections:

dart
var passed = marks.where((mark) => mark >= 50);

But a teacher rarely wants another list. She wants one number: the class total, the average, the top mark. So far the only way to get it was a loop with a variable we update by hand. Dart lists have methods that do this job — they combine all the elements into a single value.


Learning Objectives

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

  • Explain what aggregation means
  • Count matching elements with where(...).length
  • Combine elements with reduce, and explain why it fails on an empty list
  • Combine elements with fold, starting from an initial value of any type
  • Calculate an average safely
  • Find the highest and lowest value by hand and with reduce
  • Combine map and fold in one expression
  • Recognize that dart:core has no .sum or .max on lists

1. Many Values, One Answer

Aggregation means combining many values into one. You already know the loop version: start a variable at 0, then add each element.

Analogy — think of a shop's cash register. Items pass one by one, and the display shows a single running total. When the last item passes, that total is the answer.

dart
void main() {
  var marks = [80, 75, 85, 90];
  var total = 0;
  for (final mark in marks) {
    total += mark;
  }
  print('Total: $total');
}
output
Total: 330

This works, but every total needs the same four lines. The methods below package that pattern.


2. Counting With where(...).length

To count elements that match a condition, filter with where and ask for the length.

dart
void main() {
  var marks = [80, 45, 85, 62, 90];
  var highMarks = marks.where((mark) => mark >= 80).length;
  print('80 or above: $highMarks');
}
output
80 or above: 3

where keeps 80, 85, and 90; length counts them.


3. reduce — Combine Two at a Time

reduce takes a function with two parameters. It starts with the first element, then combines the result so far with the next element, again and again.

dart
void main() {
  var marks = [80, 75, 85, 90];
  var total = marks.reduce((sum, mark) => sum + mark);
  print(total);
}
output
330
StepsummarkReturns
18075155
215585240
324090330

The value returned in one step becomes sum in the next. The last value returned is the answer.


4. reduce on an Empty List

reduce begins with the first element — so an empty list gives it nothing to begin with.

dart
void main() {
  var marks = <int>[];
  print(marks.reduce((sum, mark) => sum + mark)); // ✗ empty list
}
output
Unhandled exception:
Bad state: No element

This is the same error as first on an empty list (Lecture 040). With one element, reduce never calls the function and simply returns that element.


5. fold — Start From an Initial Value

fold takes an initial value first, then the same kind of two-parameter function. The running result starts at the initial value instead of the first element.

dart
void main() {
  var marks = [80, 75, 85, 90];
  var empty = <int>[];
  var total = marks.fold(0, (sum, mark) => sum + mark);
  var nothing = empty.fold(0, (sum, mark) => sum + mark);
  print(total);
  print(nothing);
}
output
330
0

An empty list is no problem: fold just returns the initial value. That makes fold the safer choice for totals.


6. fold Can Return a Different Type

reduce must return the same type as the elements. fold returns the type of its initial value, so it can turn a list of names into a number.

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan'];
  var letters = names.fold(0, (count, name) => count + name.length);
  print('Letters: $letters');
}
output
Letters: 12

The same idea with reduce is rejected, because a List<String> must reduce to a String:

dart
void main() {
  var names = ['Ali', 'Sara'];
  var total = names.reduce((a, b) => a.length + b.length); // ✗
  print(total);
}
output
Error: A value of type 'int' can't be returned from a function with return type 'String'.
  var total = names.reduce((a, b) => a.length + b.length);
                                              ^

7. The Initial Value Sets the Type

Dart takes the result type of fold from the initial value. 0 is an int, so adding double prices to it is rejected:

dart
void main() {
  var prices = [1.5, 2.5];
  var total = prices.fold(0, (sum, price) => sum + price); // ✗
  print(total);
}
output
Error: A value of type 'double' can't be returned from a function with return type 'int'.
  var total = prices.fold(0, (sum, price) => sum + price);
                                             ^

Start from 0.0 instead:

dart
void main() {
  var prices = [1.5, 2.5];
  var total = prices.fold(0.0, (sum, price) => sum + price);
  print(total);
}
output
4.0

Store the result in a variable before printing it. print accepts any value, so a fold written straight inside it is given the vague type Object? — and + does not work on that:

dart
void main() {
  var marks = [80, 75];
  print(marks.fold(0, (sum, mark) => sum + mark)); // ✗
}
output
Error: The operator '+' isn't defined for the type 'Object?'.
 - 'Object' is from 'dart:core'.
Try correcting the operator to an existing operator, or defining a '+' operator.
  print(marks.fold(0, (sum, mark) => sum + mark));
                                         ^

How Dart works out such types is part of generics, which we study in Lecture 072 — Generics Fundamentals.


8. The Average

An average is the total divided by the count. / always gives a double.

dart
void main() {
  var marks = [80, 75, 85, 90];
  var total = marks.fold(0, (sum, mark) => sum + mark);
  print('Average: ${total / marks.length}');
}
output
Average: 82.5

On an empty list this becomes 0 / 0, which Dart prints as NaN ("not a number"). Check isEmpty first:

dart
void main() {
  var marks = <int>[];
  var total = marks.fold(0, (sum, mark) => sum + mark);
  print(total / marks.length);
  var average = marks.isEmpty ? 0.0 : total / marks.length;
  print(average);
}
output
NaN
0.0

9. Highest and Lowest by Hand

Start with the first element as both the highest and lowest so far, then compare every element with them.

dart
void main() {
  var temps = [21.5, 24.0, 19.0, 26.5, 22.0];
  var highest = temps.first;
  var lowest = temps.first;
  for (final t in temps) {
    if (t > highest) highest = t;
    if (t < lowest) lowest = t;
  }
  print('Highest: $highest');
  print('Lowest: $lowest');
}
output
Highest: 26.5
Lowest: 19.0

Why not start at 0? If every temperature were below zero, 0 would win, and it is not even in the list.


10. Highest and Lowest With reduce

reduce keeps whichever of two values "wins" and carries it forward.

dart
void main() {
  var temps = [21.5, 24.0, 19.0, 26.5, 22.0];
  var highest = temps.reduce((a, b) => a > b ? a : b);
  var lowest = temps.reduce((a, b) => a < b ? a : b);
  print('Highest: $highest');
  print('Lowest: $lowest');
}
output
Highest: 26.5
Lowest: 19.0

Here reduce fits well: the answer must be one of the elements, and an empty list really has no highest value — so the error is honest.


11. No .sum or .max Built In

Many languages give lists a ready-made sum or max. Dart's core library does not:

dart
void main() {
  var marks = [80, 75, 85];
  print(marks.sum); // ✗
}
output
Error: The getter 'sum' isn't defined for the type 'List<int>'.
 - 'List' is from 'dart:core'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'sum'.
  print(marks.sum);
              ^^^

.max gives the same error. Such helpers come from extra libraries, which we meet in Lecture 101 — Dart Libraries and Lecture 103 — Dart Packages. With fold and reduce you need none of them.


12. map and fold Together

map changes each element; fold then combines them. Each item below costs its price plus a delivery fee of 50:

dart
void main() {
  var prices = [500, 1200, 300];
  var total = prices
      .map((price) => price + 50)
      .fold(0, (sum, price) => sum + price);
  print('Total: $total');
}
output
Total: 2150

map is lazy (Lecture 043); fold asks for each changed value in turn, so no toList() is needed.


13. A Real-World Example

dart
void main() {
  final marks = [72, 45, 88, 30, 65, 91];

  var total = marks.fold(0, (sum, mark) => sum + mark);
  var average = marks.isEmpty ? 0.0 : total / marks.length;
  var highest = marks.reduce((a, b) => a > b ? a : b);
  var lowest = marks.reduce((a, b) => a < b ? a : b);
  var passed = marks.where((mark) => mark >= 50).length;

  print('Total: $total');
  print('Average: ${average.toStringAsFixed(1)}');
  print('Highest: $highest, Lowest: $lowest');
  print('Passed: $passed of ${marks.length}');
}
output
Total: 391
Average: 65.2
Highest: 91, Lowest: 30
Passed: 4 of 6

One list, five answers: fold for the total, a guarded division for the average, reduce for the extremes, and where(...).length for the count.


Code Examples

Example 1 — Sum With fold

dart
void main() {
  var numbers = [10, 20, 30];
  var sum = numbers.fold(0, (total, n) => total + n);
  print(sum);
}
output
60

Example 2 — Product With reduce

dart
void main() {
  var numbers = [2, 3, 4];
  print(numbers.reduce((product, n) => product * n));
}
output
24

Example 3 — Counting Long Names

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan', 'Hina'];
  print(names.where((name) => name.length > 3).length);
}
output
3

Example 4 — The Longest Name

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan', 'Hina'];
  var longest = names.reduce((a, b) => a.length >= b.length ? a : b);
  print(longest);
}
output
Zayan

Example 5 — Initials With fold

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan'];
  var initials = names.fold('', (text, name) => text + name[0]);
  print(initials);
}
output
ASZ

The initial value '' is a String, so the result is a String.

Example 6 — An Empty Cart Is Safe With fold

dart
void main() {
  var cart = <int>[];
  var total = cart.fold(0, (sum, price) => sum + price);
  print('Total: $total');
}
output
Total: 0

Example 7 — Student Marks

dart
void main() {
  final marks = [78, 92, 85];
  var total = marks.fold(0, (sum, mark) => sum + mark);
  print('Total: $total');
  print('Average: ${total / marks.length}');
  print('Highest: ${marks.reduce((a, b) => a > b ? a : b)}');
}
output
Total: 255
Average: 85.0
Highest: 92

Example 8 — Shopping Total

dart
void main() {
  final prices = [450, 1200, 300, 800];
  var total = prices.fold(0, (sum, price) => sum + price);
  var expensive = prices.where((price) => price > 500).length;
  print('Total: $total');
  print('Items over 500: $expensive');
}
output
Total: 2750
Items over 500: 2

Example 9 — Bank Balance

dart
void main() {
  final transactions = [500, -200, 1000, -50, 300, -120];
  var balance = transactions.fold(1000, (sum, amount) => sum + amount);
  var deposits = transactions.where((amount) => amount > 0).length;
  print('Balance: $balance');
  print('Deposits: $deposits');
}
output
Balance: 2430
Deposits: 3

How it works

  1. The initial value 1000 is the opening balance, so fold starts there, not at 0.
  2. Withdrawals are negative, so adding them lowers the balance.

Example 10 — Temperature Week

dart
void main() {
  final temps = [21.5, 24.0, 19.0, 26.5, 22.0, 23.5, 20.5];
  var sum = temps.fold(0.0, (total, t) => total + t);
  var highest = temps.reduce((a, b) => a > b ? a : b);
  var lowest = temps.reduce((a, b) => a < b ? a : b);
  print('Average: ${(sum / temps.length).toStringAsFixed(1)}');
  print('Range: ${highest - lowest}');
}
output
Average: 22.4
Range: 7.5

How it works

  1. The initial value is 0.0 because the temperatures are double.
  2. The range is the gap between the highest and lowest reading: 26.5 - 19.0.

Common Beginner Mistakes

1. Using reduce on a list that may be empty

dart
// var total = <int>[].reduce((sum, n) => sum + n);
output
Unhandled exception:
Bad state: No element

For totals, use fold(0, …). For highest or lowest, check isNotEmpty first.

2. Starting fold at 0 for double values

dart
// var total = [1.5, 2.5].fold(0, (sum, price) => sum + price);
output
Error: A value of type 'double' can't be returned from a function with return type 'int'.

Start at 0.0.

3. Calling fold directly inside print(...)

output
Error: The operator '+' isn't defined for the type 'Object?'.

Store the result in a variable first, then print the variable.

4. Expecting reduce to change the type

reduce on a List<String> must return a String. To get a number, use fold with a number as the initial value.

5. Looking for .sum or .max

output
Error: The getter 'sum' isn't defined for the type 'List<int>'.

Write fold or reduce instead.

6. Dividing by the length of an empty list

total / marks.length on an empty list prints NaN. Guard with isEmpty.

7. Starting the highest value at 0

With [-5, -2, -8], a "highest" that starts at 0 stays 0. Start from first, or use reduce.


Best Practices

  • Prefer fold for totals and counts — it handles empty lists.
  • Use reduce when the answer is one of the elements and the list cannot be empty.
  • Match the initial value's type to the answer: 0, 0.0, or ''.
  • Name the two parameters for their roles: (sum, mark), not (a, b), when it helps.
  • Guard every average with isEmpty.
  • Store a fold result in a variable before printing it.
  • Count with where(...).length instead of a hand-written counter.

Mini Challenge

Zayan's game scores are [12, 7, 15, 9]. Print the total, the best score, and how many scores are above 10.

output
Total: 43
Best: 15
Above 10: 2

Try it yourself before opening the solution.

Show solution
dart
void main() {
  final scores = [12, 7, 15, 9];
  print('Total: ${scores.fold(0, (sum, s) => sum + s)}');
  print('Best: ${scores.reduce((a, b) => a > b ? a : b)}');
  print('Above 10: ${scores.where((s) => s > 10).length}');
}
output
Total: 43
Best: 15
Above 10: 2

Practice Questions

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

Beginner

  1. Use fold to add up [5, 10, 15] and print the result.
  2. Use reduce to multiply [1, 2, 3, 4, 5] together and print the result.
  3. Count the even numbers in [3, 8, 12, 5, 20] with where and print the count.

Application

  1. Print the average of [70, 80, 90].
  2. Use reduce to print the highest value in [34, 67, 12, 89, 45].
  3. Find the lowest value in [34, 67, 12, 89, 45] with a loop, and print it.
  4. Use fold to count the total number of characters in ['Dart', 'is', 'fun'].
  5. Sara buys items priced [250, 400, 150], two of each. Use map and fold to print the total.

Challenge

  1. Write double average(List<double> values) that returns 0.0 for an empty list. Print average([18.5, 21.0, 25.5]) with two decimals, then average([]).
  2. Ali's transactions are [500, -200, 1000, -50, 300]. Print the total of the deposits, the total of the withdrawals, and the final balance (starting from 0), labelled Deposits:, Withdrawals:, and Balance:.

Practice Answers

Question 1 — Solution

dart
void main() {
  var total = [5, 10, 15].fold(0, (sum, n) => sum + n);
  print(total);
}
output
30

Question 2 — Solution

dart
void main() {
  print([1, 2, 3, 4, 5].reduce((product, n) => product * n));
}
output
120

Question 3 — Solution

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

Question 4 — Solution

dart
void main() {
  var marks = [70, 80, 90];
  var total = marks.fold(0, (sum, mark) => sum + mark);
  print(total / marks.length);
}
output
80.0

Question 5 — Solution

dart
void main() {
  print([34, 67, 12, 89, 45].reduce((a, b) => a > b ? a : b));
}
output
89

Question 6 — Solution

dart
void main() {
  var numbers = [34, 67, 12, 89, 45];
  var lowest = numbers.first;
  for (final n in numbers) {
    if (n < lowest) lowest = n;
  }
  print(lowest);
}
output
12

Question 7 — Solution

dart
void main() {
  var words = ['Dart', 'is', 'fun'];
  var letters = words.fold(0, (count, word) => count + word.length);
  print(letters);
}
output
9

Question 8 — Solution

dart
void main() {
  var prices = [250, 400, 150];
  var total = prices
      .map((price) => price * 2)
      .fold(0, (sum, price) => sum + price);
  print(total);
}
output
1600

Question 9 — Solution

dart
double average(List<double> values) {
  if (values.isEmpty) return 0.0;
  var total = values.fold(0.0, (sum, v) => sum + v);
  return total / values.length;
}

void main() {
  print(average([18.5, 21.0, 25.5]).toStringAsFixed(2));
  print(average([]));
}
output
21.67
0.0

[] passed to a List<double> parameter becomes an empty List<double>.

Question 10 — Solution

dart
void main() {
  final transactions = [500, -200, 1000, -50, 300];
  var deposits = transactions.where((t) => t > 0).fold(0, (sum, t) => sum + t);
  var withdrawals = transactions
      .where((t) => t < 0)
      .fold(0, (sum, t) => sum + t);
  print('Deposits: $deposits');
  print('Withdrawals: $withdrawals');
  print('Balance: ${deposits + withdrawals}');
}
output
Deposits: 1800
Withdrawals: -250
Balance: 1550

where and fold chain just like map and fold.


Quick Revision

  1. What does "aggregation" mean?
  2. How do you count the elements that match a condition?
  3. Where does reduce start, and where does fold start?
  4. What happens when you call reduce on an empty list?
  5. Why does fold(0, …) fail on a list of double values?
  6. Which method can turn a List<String> into an int?
  7. How do you avoid NaN when averaging?
  8. Does dart:core give lists a .sum getter?

Key Takeaways

  • Aggregation combines many elements into one value.
  • where(condition).length counts matching elements.
  • reduce starts from the first element and must return the element type.
  • reduce on an empty list throws Bad state: No element.
  • fold starts from an initial value, returns that value for an empty list, and can produce a different type.
  • The initial value decides fold's result type: use 0.0 for double values.
  • An average is total / length; guard against an empty list to avoid NaN.
  • Highest and lowest: start from first in a loop, or use reduce with ? :.
  • dart:core has no .sum or .max on lists — fold and reduce do the job.
  • map or where followed by fold transforms and combines in one expression.

What's Next?

Lecture 45 — Sorting & Reversing Lists. We can now summarize a list, but its elements are still in whatever order they arrived. Next we put them in order — with sort, with our own comparison function, and with reversed.


Video lecture

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