On this page25 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is a Higher-Order Function?
- 2. Reading a Function-Type Parameter
- 3. One Function, Many Rules
- 4. Passing an Arrow Function on the Spot
- 5. The Higher-Order Function Does the Shared Work
- 6. repeat — Passing an Action
- 7. Handing Values to the Function You Call
- 8. Calling the Function More Than Once
- 9. Dart Checks the Function's Shape
- 10. Returning a Function
- 11. Choosing Which Function to Return
- 12. A Higher-Order Function Over a List
- 13. 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 36 — Higher-Order Functions
| Category | 6 — Functions |
| Lecture | 036 of 140 |
| Estimated video length | 11 min |
| Target | Dart 3.12.2 |
| Level | Beginner |
Two functions that differ in a single line are still two functions to write, test, and fix. A higher-order function keeps the shared work in one place and takes the one line that changes as a function argument.
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–035:
forandfor-inloops, and switch expressions (Lecture 025)- functions with parameters and return values (Lectures 030–033)
- arrow functions and anonymous functions (Lecture 034)
- function types such as
int Function(int), and passing a function as a value (Lecture 035)
Connection With Previous Lectures
Lecture 035 showed that a function is a value: it can be stored in a variable and passed around. But why would we want to? Look at these two functions from a shop's checkout:
void checkoutTenPercent(double price) {
double finalPrice = price * 0.9;
print('Price: $price -> You pay: $finalPrice');
}
void checkoutFlatFifty(double price) {
double finalPrice = price - 50;
print('Price: $price -> You pay: $finalPrice');
}Only the middle line differs. A third discount means a third copy, and a change to the receipt means editing every copy. This lecture removes the copies: write the checkout once, and pass the changing line in as a function.
Learning Objectives
By the end of this lecture you will be able to:
- Explain what makes a function higher-order
- Write a function that takes another function as a parameter
- Read and write function-type parameters such as
double Function(double) rule - Pass a named function or an arrow function as an argument
- Call a function parameter once, many times, or with values of your own
- Write a function that returns a function
- Recognize the errors Dart gives when a function of the wrong shape is passed
- Split a task into a fixed "how" (the higher-order function) and a changing "what" (the function you pass)
1. What Is a Higher-Order Function?
A higher-order function is a function that takes a function as a parameter, returns a function, or both. Every other function is an ordinary, "first-order" one.
Analogy — think of a coffee machine. It always heats the water, presses the coffee, and fills the cup. You only choose the recipe card you slide in. The machine is the higher-order function; the recipe card is the function you pass.
double applyDiscount(double price, double Function(double) rule) {
return rule(price);
}
double tenPercentOff(double price) => price * 0.9;
void main() {
print(applyDiscount(200, tenPercentOff));
}180.0applyDiscount does not know any discount. It receives one as rule and calls it with
rule(price).
2. Reading a Function-Type Parameter
The second parameter of applyDiscount reads left to right like this:
| Part | Meaning |
|---|---|
double | the passed function must return a double |
Function | this parameter holds a function |
(double) | the passed function must take one double |
rule | the parameter's name inside applyDiscount |
You call a function parameter exactly like any function: its name, then parentheses with
arguments. Pass the function itself — tenPercentOff, with no parentheses. Writing
tenPercentOff() would try to call it right there.
3. One Function, Many Rules
The payoff: the same applyDiscount works with every rule that has the right shape.
double applyDiscount(double price, double Function(double) rule) {
return rule(price);
}
double tenPercentOff(double price) => price * 0.9;
double flatFiftyOff(double price) => price - 50;
double noDiscount(double price) => price;
void main() {
print(applyDiscount(200, tenPercentOff));
print(applyDiscount(200, flatFiftyOff));
print(applyDiscount(200, noDiscount));
}180.0
150.0
200.0A new discount is one new short function. applyDiscount never changes.
4. Passing an Arrow Function on the Spot
A rule used only once does not need a name. Write it right inside the call:
double applyDiscount(double price, double Function(double) rule) {
return rule(price);
}
void main() {
print(applyDiscount(200, (price) => price * 0.75));
print(applyDiscount(200, (price) => price / 2));
}150.0
100.0Notice that price has no type. Dart reads it from applyDiscount's parameter type — the
rule must take a double, so price is a double.
5. The Higher-Order Function Does the Shared Work
Now we can rebuild the checkout from the start of the lecture. The receipt line and a safety check live in one place; the rule only does the maths.
double applyDiscount(double price, double Function(double) rule) {
double newPrice = rule(price);
if (newPrice < 0) {
newPrice = 0;
}
print('Price: $price -> You pay: $newPrice');
return newPrice;
}
double tenPercentOff(double price) => price * 0.9;
double flatFiftyOff(double price) => price - 50;
void main() {
applyDiscount(200, tenPercentOff);
applyDiscount(30, flatFiftyOff);
}Price: 200.0 -> You pay: 180.0
Price: 30.0 -> You pay: 0.030 - 50 would be -20, but no rule can ever produce a negative price, because the check
sits in the higher-order function that every rule passes through.
6. repeat — Passing an Action
A function type with void and empty parentheses, void Function(), means "an action that
takes nothing and returns nothing". That lets us write our own repeat:
void repeat(int times, void Function() action) {
for (int i = 0; i < times; i++) {
action();
}
}
void sayHello() => print('Hello!');
void main() {
repeat(3, sayHello);
repeat(2, () => print('Dart is fun'));
}Hello!
Hello!
Hello!
Dart is fun
Dart is funThe loop is written once. What happens in each round is the caller's choice.
7. Handing Values to the Function You Call
The higher-order function can pass its own values into the function it calls. Here repeat
tells the action which round it is:
void repeat(int times, void Function(int round) action) {
for (int round = 1; round <= times; round++) {
action(round);
}
}
void main() {
repeat(3, (round) => print('Lap $round done'));
}Lap 1 done
Lap 2 done
Lap 3 doneThe name inside void Function(int round) is optional. It is there for the reader — the
type would mean the same written as void Function(int).
8. Calling the Function More Than Once
A function parameter can be called as often as you like, even on its own result:
int applyTwice(int Function(int) step, int value) {
return step(step(value));
}
int addTen(int n) => n + 10;
int doubleIt(int n) => n * 2;
void main() {
print(applyTwice(addTen, 5));
print(applyTwice(doubleIt, 5));
}25
20The inner step(value) runs first: addTen(5) is 15, then addTen(15) is 25.
9. Dart Checks the Function's Shape
A function parameter has a type, so Dart checks every function you pass. sayHello takes
nothing and returns nothing — not what a discount rule needs:
double applyDiscount(double price, double Function(double) rule) {
return rule(price);
}
void sayHello() => print('Hello!');
void main() {
print(applyDiscount(200, sayHello)); // ✗
}Error: The argument type 'void Function()' can't be assigned to the parameter type 'double Function(double)'.
print(applyDiscount(200, sayHello)); // ✗
^The message names both shapes, so you can see exactly what does not match.
10. Returning a Function
The other kind of higher-order function builds a function and hands it back. Its return type is a function type:
int Function(int) makeMultiplier(int factor) {
return (int n) => n * factor;
}
void main() {
int Function(int) triple = makeMultiplier(3);
int Function(int) tenTimes = makeMultiplier(10);
print(triple(5));
print(tenTimes(5));
print(makeMultiplier(2)(7));
}15
50
14One function makes as many multipliers as we need. In the last line, makeMultiplier(2)
returns a function, and (7) calls it straight away. Notice that triple still uses
factor long after makeMultiplier has finished — how a function remembers that value is
the subject of Lecture 037 — Closures & Scope.
11. Choosing Which Function to Return
A higher-order function can also pick from functions that already exist. A switch expression makes this neat:
double tenPercentOff(double price) => price * 0.9;
double flatFiftyOff(double price) => price - 50;
double noDiscount(double price) => price;
double Function(double) pickRule(String code) {
return switch (code) {
'SAVE10' => tenPercentOff,
'FLAT50' => flatFiftyOff,
_ => noDiscount,
};
}
void main() {
double Function(double) rule = pickRule('FLAT50');
print(rule(300));
print(pickRule('HELLO')(300));
}250.0
300.0An unknown code falls through to _ and gets noDiscount, so the caller always receives a
working rule.
12. A Higher-Order Function Over a List
A function that answers yes or no — bool Function(int) — is called a test. Passing one
lets a single loop count anything:
int countIf(List<int> numbers, bool Function(int) test) {
int count = 0;
for (int n in numbers) {
if (test(n)) {
count++;
}
}
return count;
}
void main() {
var marks = [85, 42, 67, 91, 38];
print(countIf(marks, (mark) => mark >= 50));
print(countIf(marks, (mark) => mark.isEven));
}3
2List<int> is the type of a list of whole numbers; lists get their own category starting
with Lecture 039. Lists also come with higher-order functions built in — you already met forEach in
Lecture 034, and map, where, and reduce also take a function, just like countIf. We will study them in
Lectures 043 — List Transformation and 044 — List Aggregation.
13. A Real-World Example
Sara buys items worth 400 with the code SAVE10, and pays in three instalments:
double tenPercentOff(double price) => price * 0.9;
double flatFiftyOff(double price) => price - 50;
double noDiscount(double price) => price;
double Function(double) pickRule(String code) {
return switch (code) {
'SAVE10' => tenPercentOff,
'FLAT50' => flatFiftyOff,
_ => noDiscount,
};
}
double applyDiscount(double price, double Function(double) rule) {
double newPrice = rule(price);
if (newPrice < 0) {
newPrice = 0;
}
return newPrice;
}
void repeat(int times, void Function(int round) action) {
for (int round = 1; round <= times; round++) {
action(round);
}
}
void main() {
double cart = 400;
double total = applyDiscount(cart, pickRule('SAVE10'));
print('Cart: $cart, Total: $total');
repeat(3, (round) => print('Instalment $round: ${total / 3}'));
}Cart: 400.0, Total: 360.0
Instalment 1: 120.0
Instalment 2: 120.0
Instalment 3: 120.0Each function has one job: pickRule chooses, applyDiscount calculates safely, repeat
loops, and the small arrow functions supply the details.
Code Examples
Example 1 — Wrapping an Action
void runTask(void Function() task) {
print('Start');
task();
print('End');
}
void main() {
runTask(() => print('Working...'));
}Start
Working...
EndExample 2 — A Row of Stars
void repeat(int times, void Function() action) {
for (int i = 0; i < times; i++) {
action();
}
}
void main() {
repeat(3, () => print('* * *'));
}* * *
* * *
* * *Example 3 — Applying a String Rule Twice
String applyTwice(String Function(String) step, String value) {
return step(step(value));
}
String excite(String text) => '$text!';
void main() {
print(applyTwice(excite, 'Hi Zayan'));
}Hi Zayan!!Example 4 — Passing an Operation
int combine(int a, int b, int Function(int, int) operation) {
return operation(a, b);
}
void main() {
print(combine(3, 4, (a, b) => a + b));
print(combine(3, 4, (a, b) => a * b));
}7
12Example 5 — Making Adders
int Function(int) makeAdder(int amount) {
return (int n) => n + amount;
}
void main() {
int Function(int) addFive = makeAdder(5);
print(addFive(10));
print(makeAdder(100)(1));
}15
101Example 6 — Counting with a Test
int countIf(List<int> numbers, bool Function(int) test) {
int count = 0;
for (int n in numbers) {
if (test(n)) {
count++;
}
}
return count;
}
void main() {
print(countIf([3, 8, 10, 7, 12], (n) => n > 5));
}4Example 7 — Student Marks
void gradeAll(List<int> marks, String Function(int) grader) {
for (int mark in marks) {
print('$mark: ${grader(mark)}');
}
}
String passFail(int mark) => mark >= 50 ? 'Pass' : 'Fail';
void main() {
gradeAll([72, 45, 90], passFail);
}72: Pass
45: Fail
90: PassHow it works
gradeAllowns the loop and the output format.passFailowns only the decision for one mark.- A letter-grade version would be a new
grader—gradeAllstays the same.
Example 8 — Shopping Total
double checkout(double total, double Function(double) rule) {
double finalTotal = rule(total);
print('Ali pays $finalTotal');
return finalTotal;
}
void main() {
double total = 150 + 250 + 100;
checkout(total, (t) => t > 400 ? t * 0.8 : t);
checkout(300, (t) => t > 400 ? t * 0.8 : t);
}Ali pays 400.0
Ali pays 300.0How it works
- The rule gives 20% off totals over
400, and leaves smaller totals unchanged. 500is over400, so Ali pays500 * 0.8, which is400.0.300is not, so the rule returns it as it is.
Example 9 — Bank Balance
int Function(int) deposit(int amount) {
return (int balance) => balance + amount;
}
int applyTo(int balance, int Function(int) action) {
int newBalance = action(balance);
print('Balance: $balance -> $newBalance');
return newBalance;
}
void main() {
int balance = 1000;
balance = applyTo(balance, deposit(500));
balance = applyTo(balance, (b) => b - 200);
}Balance: 1000 -> 1500
Balance: 1500 -> 1300How it works
deposit(500)returns a function that adds500to any balance.applyTotakes that function, calls it, prints, and returns the result.- The withdrawal is an arrow function written on the spot.
Example 10 — Temperature Conversion
void convertAll(List<double> temps, double Function(double) convert) {
for (double t in temps) {
print('$t -> ${convert(t)}');
}
}
double toFahrenheit(double c) => c * 9 / 5 + 32;
void main() {
convertAll([0, 25, 100], toFahrenheit);
}0.0 -> 32.0
25.0 -> 77.0
100.0 -> 212.0How it works
[0, 25, 100]is passed where aList<double>is expected, so each value is adouble.convertAllprints each temperature beside its converted value.- Passing a
toKelvinfunction instead would reuse the same loop.
Common Beginner Mistakes
1. Calling the function instead of passing it
// double tenPercentOff(double price) => price * 0.9;
// applyDiscount(200, tenPercentOff(200));Error: The argument type 'double' can't be assigned to the parameter type 'double Function(double)'.tenPercentOff(200) runs the rule and passes its result, 180.0. Pass tenPercentOff with
no parentheses.
2. Passing a function of the wrong shape
// void sayHello() => print('Hello!');
// applyDiscount(200, sayHello);Error: The argument type 'void Function()' can't be assigned to the parameter type 'double Function(double)'.The passed function must match the parameter's return type and its parameter types.
3. Forgetting the parentheses when calling the parameter
void repeat(int times, void Function() action) {
for (int i = 0; i < times; i++) {
action; // ✗ names the function, never calls it
}
}
void main() {
repeat(3, () => print('Hello!'));
print('Finished');
}FinishedNo error, and no Hello! either. action alone only mentions the function. Write
action().
4. Forgetting the return type of a returned function
makeMultiplier(int factor) without int Function(int) in front becomes dynamic, and Dart
can no longer check how its result is called. Always write the function type.
Best Practices
- Use a higher-order function when several functions differ only in one small step.
- Name function parameters by their role:
rule,action,test,convert. - Keep the shared, safety-critical work (checks, output) in the higher-order function.
- Use arrow functions for one-off arguments; name a function when you reuse it.
- Always write full function types, such as
bool Function(int), for parameters and returns. - Don't make a function higher-order "just because" — one fixed behaviour needs no parameter.
Mini Challenge
Write int applyN(int times, int Function(int) step, int start) that starts with start
and applies step to it times times. Use it to give Zayan's score of 50 three bonuses of
10, and to double 1 five times.
Your program should print exactly:
80
32Try it yourself before opening the solution.
Show solution
int applyN(int times, int Function(int) step, int start) {
int value = start;
for (int i = 0; i < times; i++) {
value = step(value);
}
return value;
}
void main() {
print(applyN(3, (score) => score + 10, 50));
print(applyN(5, (n) => n * 2, 1));
}80
32How it works
valuestarts asstartand is replaced bystep(value)each round.50 → 60 → 70 → 80, and1 → 2 → 4 → 8 → 16 → 32.applyNknows nothing about scores or doubling — the step decides.
Practice Questions
Work through these in order — they run from beginner to application to challenge. Every question gives you exact values, so you can compare your output with the answers.
Beginner
- Write
void runTwice(void Function() action)that callsactiontwice. Call it with an arrow function that printsHi Ali. - Write
int applyRule(int value, int Function(int) rule)and a named functionaddOne. PrintapplyRule(9, addOne). - Write
void repeat(int times, void Function(int round) action)and use it to printStep 1,Step 2, andStep 3.
Application
- Write
String format(String name, String Function(String) style). For'Sara', print the result with a style that upper-cases the name, then with one that returnsDr. Sara. - Write
int calculate(int a, int b, int Function(int, int) op). For20and4, print the difference and the whole-number division (~/). - Using
countIffrom section 12, count how many of[45, 72, 50, 38, 90]are at least50. - Using
applyDiscountfrom section 1, print a250price with 20% off, and with a flat30off — both as arrow functions. - Write
String Function(String) makeGreeter(String greeting). StoremakeGreeter('Hello')and call it with'Zayan'to printHello, Zayan!.
Challenge
- Write
int Function(int, int) pickOperation(String symbol)with a switch expression for'+','-', and'*', returning a function that gives0for anything else. PrintpickOperation('*')(6, 7),pickOperation('-')(6, 7), andpickOperation('?')(6, 7). - Write
int retry(int maxTries, bool Function(int attempt) tryOnce). It callstryOncewith1,2,3, … and returns the first attempt number that givestrue, or-1if allmaxTriesfail. Test it with(attempt) => attempt * 25 >= 70and5tries, then with(attempt) => falseand3tries.
Practice Answers
Question 1 — Solution
void runTwice(void Function() action) {
action();
action();
}
void main() {
runTwice(() => print('Hi Ali'));
}Hi Ali
Hi AliQuestion 2 — Solution
int applyRule(int value, int Function(int) rule) {
return rule(value);
}
int addOne(int n) => n + 1;
void main() {
print(applyRule(9, addOne));
}10Question 3 — Solution
void repeat(int times, void Function(int round) action) {
for (int round = 1; round <= times; round++) {
action(round);
}
}
void main() {
repeat(3, (round) => print('Step $round'));
}Step 1
Step 2
Step 3Question 4 — Solution
String format(String name, String Function(String) style) {
return style(name);
}
void main() {
print(format('Sara', (n) => n.toUpperCase()));
print(format('Sara', (n) => 'Dr. $n'));
}SARA
Dr. SaraQuestion 5 — Solution
int calculate(int a, int b, int Function(int, int) op) {
return op(a, b);
}
void main() {
print(calculate(20, 4, (a, b) => a - b));
print(calculate(20, 4, (a, b) => a ~/ b));
}16
5Question 6 — Solution
int countIf(List<int> numbers, bool Function(int) test) {
int count = 0;
for (int n in numbers) {
if (test(n)) {
count++;
}
}
return count;
}
void main() {
print(countIf([45, 72, 50, 38, 90], (mark) => mark >= 50));
}3Question 7 — Solution
double applyDiscount(double price, double Function(double) rule) {
return rule(price);
}
void main() {
print(applyDiscount(250, (price) => price * 0.8));
print(applyDiscount(250, (price) => price - 30));
}200.0
220.0Question 8 — Solution
String Function(String) makeGreeter(String greeting) {
return (String name) => '$greeting, $name!';
}
void main() {
String Function(String) hello = makeGreeter('Hello');
print(hello('Zayan'));
}Hello, Zayan!Question 9 — Solution
int Function(int, int) pickOperation(String symbol) {
return switch (symbol) {
'+' => (int a, int b) => a + b,
'-' => (int a, int b) => a - b,
'*' => (int a, int b) => a * b,
_ => (int a, int b) => 0,
};
}
void main() {
print(pickOperation('*')(6, 7));
print(pickOperation('-')(6, 7));
print(pickOperation('?')(6, 7));
}42
-1
0Question 10 — Solution
int retry(int maxTries, bool Function(int attempt) tryOnce) {
for (int attempt = 1; attempt <= maxTries; attempt++) {
if (tryOnce(attempt)) {
return attempt;
}
}
return -1;
}
void main() {
print(retry(5, (attempt) => attempt * 25 >= 70));
print(retry(3, (attempt) => false));
}3
-125 and 50 are below 70; 75 is not, so attempt 3 succeeds. The early return ends
the loop at once, and -1 is reached only when every attempt fails.
Quick Revision
- What two things can make a function higher-order?
- How do you read the type
bool Function(int)? - Why do you pass
tenPercentOffand nottenPercentOff()? - Where does the type of
pricecome from inapplyDiscount(200, (price) => price / 2)? - What does
void Function()describe? - What does
makeMultiplier(2)(7)do, step by step? - What happens if a function parameter is written as
action;instead ofaction();?
Key Takeaways
- A higher-order function takes a function as a parameter, returns one, or both.
- It keeps the shared "how" in one place; the passed function supplies the changing "what".
- A function-type parameter states the return type and parameter types:
double Function(double). - Pass a function by its name with no parentheses, or write an arrow function on the spot.
- The higher-order function decides when, how often, and with what values to call it.
- Dart checks the shape of every function you pass and names both shapes in the error.
- A function can build and return a new function, such as
makeMultiplier(3). - Lists have built-in higher-order functions (
forEach,map,where,reduce) — coming in Lectures 043–044.
What's Next?
Lecture 037 — Closures & Scope. makeMultiplier(3) finished running long ago, yet
triple still knows that factor is 3. Next we look at scope — which variables a piece
of code can see — and at closures, the functions that carry their variables with them.