On this page22 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What "First-Class" Means
- 2. Storing a Function in a Variable
- 3. add versus add(2, 3)
- 4. Function Types
- 5. Dart Checks Function Types
- 6. Storing an Anonymous Function
- 7. Passing a Function as an Argument
- 8. Returning a Function
- 9. Choosing Behaviour at Runtime
- 10. 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 35 — Functions as First-Class Objects
| Category | 6 — Functions |
| Lecture | 035 of 140 |
| Estimated video length | 11 min |
| Target | Dart 3.12.2 |
| Level | Beginner |
Until now a function could only be called. In Dart a function is also a value: you can store it in a variable, hand it to another function, and choose which one runs while the program is running.
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–034:
- variables,
var, type inference, anddynamic(Lectures 004, 005, 011) ifstatements and switch expressions (Lectures 022–025)- functions with typed parameters and return types (Lectures 030–031)
- arrow functions
=>and anonymous functions (Lecture 034)
Connection With Previous Lectures
Here is a program that prints a result with one of two calculations:
int add(int a, int b) => a + b;
int multiply(int a, int b) => a * b;
void main() {
String choice = 'add';
if (choice == 'add') {
print('Result: ${add(6, 3)}');
} else {
print('Result: ${multiply(6, 3)}');
}
}Result: 9The print line is written twice; only the function differs. Add subtract and divide
and the copies multiply. What we really want is one line that says "print the result of
whichever function was chosen". For that, the chosen function itself must be something
we can keep in a variable — a value, just like 9 or 'add'.
Learning Objectives
By the end of this lecture you will be able to:
- Explain what "functions are first-class objects" means
- Store a function in a variable using a tear-off (no
()) - Tell the difference between
addandadd(2, 3) - Write function types such as
int Function(int, int) - Pass a function as an argument to another function
- Return a function from a function
- Choose which function runs while the program is running
- Recognize the errors Dart gives for mismatched function types
1. What "First-Class" Means
A value is first-class when you can do everything with it that you can do with a number or a string: store it in a variable, pass it to a function, and return it from a function. In Dart, functions are first-class.
Analogy — a function is like a recipe card. You can cook the recipe (call the function), but you can also hand the card to someone else, who cooks it later. Handing the card over does not cook anything.
2. Storing a Function in a Variable
Write the function's name without parentheses. This is called a tear-off: you tear the function off and keep it, without running it.
void greet(String name) {
print('Hello, $name!');
}
void main() {
var sayHello = greet; // stored, not called
sayHello('Ali');
sayHello('Sara');
}Hello, Ali!
Hello, Sara!sayHello now holds the same function as greet. Calling sayHello('Ali') runs greet's
body with name set to 'Ali'.
3. add versus add(2, 3)
The parentheses make all the difference:
addis the function itself — the recipe card.add(2, 3)calls it and gives back the result — the cooked meal.
int add(int a, int b) {
return a + b;
}
void main() {
print(add);
print(add(2, 3));
}Closure: (int, int) => int from Function 'add': static.
5Printing the function shows its shape: it takes (int, int) and returns int. Dart calls
function values "closures" in this message. We will see where that word comes from in
Lecture 037.
4. Function Types
Every function value has a type, and Dart writes it like this:
returnType Function(parameterTypes)So add has the type int Function(int, int), and greet has void Function(String).
You can write that type on a variable instead of var:
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
void main() {
int Function(int, int) operation = add;
print(operation(10, 4));
operation = subtract;
print(operation(10, 4));
}14
6The variable can be reassigned to any function with the same shape. Here the same line
operation(10, 4) gives a different answer depending on which function it holds.
5. Dart Checks Function Types
A function type is checked like any other type. A function with the wrong shape is rejected before the program runs:
int square(int n) => n * n;
void main() {
int Function(int, int) operation = square; // ✗
print(operation(2, 3));
}Error: A value of type 'int Function(int)' can't be assigned to a variable of type 'int Function(int, int)'.
int Function(int, int) operation = square; // ✗
^square takes one int, but the variable promises two. Even with var, Dart infers the
function type and protects it — calling sayHello(25) from section 2 would fail with
"The argument type 'int' can't be assigned to the parameter type 'String'".
6. Storing an Anonymous Function
In Lecture 034 we wrote functions without names. A variable is a natural home for them:
void main() {
var doubleIt = (int n) => n * 2;
String Function(String) shout = (String text) {
return '${text.toUpperCase()}!';
};
print(doubleIt(21));
print(shout('hello'));
}42
HELLO!Named or anonymous, it does not matter: once stored, both are called the same way.
7. Passing a Function as an Argument
A parameter can have a function type too. The caller then decides which function to hand over:
int add(int a, int b) => a + b;
int multiply(int a, int b) => a * b;
void showResult(int a, int b, int Function(int, int) operation) {
print('Result: ${operation(a, b)}');
}
void main() {
showResult(6, 3, add);
showResult(6, 3, multiply);
showResult(6, 3, (a, b) => a - b);
}Result: 9
Result: 18
Result: 3This solves the problem from the start of the lecture: showResult has one print line,
and the caller chooses the calculation. Notice add with no () — we hand the card
over; showResult cooks it. In the last call, Dart knows from the parameter type that a
and b are int, so the anonymous function needs no types.
A function that takes or returns another function is called a higher-order function. Lecture 036 is all about them.
8. Returning a Function
A function can also give back a function. Its return type is a function type:
double toFahrenheit(double c) => c * 9 / 5 + 32;
double toKelvin(double c) => c + 273.15;
double Function(double) converterFor(String unit) {
if (unit == 'F') {
return toFahrenheit;
}
return toKelvin;
}
void main() {
var convert = converterFor('F');
print(convert(25));
print(converterFor('K')(25));
}77.0
298.15converterFor('F') returns the function toFahrenheit, which we store and then call. The
last line does both at once: the first () gets a function, the second () calls it.
9. Choosing Behaviour at Runtime
Put it together with a switch expression from Lecture 025, and the program picks a function based on data it only knows while running:
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
int multiply(int a, int b) => a * b;
int Function(int, int) pick(String symbol) {
return switch (symbol) {
'+' => add,
'-' => subtract,
_ => multiply,
};
}
void main() {
String symbol = '-';
var operation = pick(symbol);
print('20 $symbol 8 = ${operation(20, 8)}');
}20 - 8 = 12Change symbol to '+' and the same code prints 20 + 8 = 28. No if chain repeats the
print; the decision is made once, and the chosen function is just a value from then on.
10. A Real-World Example
double noDiscount(double total) => total;
double tenPercentOff(double total) => total * 0.9;
double flatFiveHundredOff(double total) => total >= 2000 ? total - 500 : total;
void checkout(String customer, double total, double Function(double) discount) {
print('$customer pays ${discount(total)}');
}
void main() {
checkout('Ali', 1500, noDiscount);
checkout('Sara', 1500, tenPercentOff);
checkout('Zayan', 2500, flatFiveHundredOff);
}Ali pays 1500.0
Sara pays 1350.0
Zayan pays 2000.0checkout does not know or care which discount rule applies — it receives the rule as a
double Function(double) and calls it. A new rule is just a new function; checkout never
changes.
Code Examples
Example 1 — A Tear-Off in a Variable
void welcome(String name) {
print('Welcome, $name!');
}
void main() {
var hello = welcome;
hello('Zayan');
}Welcome, Zayan!Example 2 — An Explicit Function Type
bool isAdult(int age) => age >= 18;
void main() {
bool Function(int) check = isAdult;
print(check(20));
print(check(6));
}true
falseExample 3 — Reassigning a Function Variable
String polite(String name) => 'Good morning, $name.';
String casual(String name) => 'Hey $name!';
void main() {
String Function(String) greeting = polite;
print(greeting('Sara'));
greeting = casual;
print(greeting('Sara'));
}Good morning, Sara.
Hey Sara!Example 4 — An Anonymous Function in a Variable
void main() {
int Function(int) triple = (n) => n * 3;
print(triple(7));
}21Example 5 — A Function Parameter
void repeatTwice(void Function() action) {
action();
action();
}
void sayHi() {
print('Hi!');
}
void main() {
repeatTwice(sayHi);
}Hi!
Hi!void Function() is the type of a function with no parameters that returns nothing.
Example 6 — Returning a Function
String morning(String name) => 'Good morning, $name';
String evening(String name) => 'Good evening, $name';
String Function(String) greeterFor(int hour) {
if (hour < 12) {
return morning;
}
return evening;
}
void main() {
print(greeterFor(9)('Ali'));
print(greeterFor(19)('Ali'));
}Good morning, Ali
Good evening, AliExample 7 — Student Marks
String strictGrade(int marks) => marks >= 80 ? 'Pass' : 'Fail';
String easyGrade(int marks) => marks >= 50 ? 'Pass' : 'Fail';
void report(String name, int marks, String Function(int) grader) {
print('$name: ${grader(marks)}');
}
void main() {
report('Ali', 65, strictGrade);
report('Ali', 65, easyGrade);
}Ali: Fail
Ali: PassHow it works
- Both graders have the type
String Function(int). reportreceives one of them and calls it with the marks.- The same
65gives different results because a different rule was passed in.
Example 8 — Shopping Total
double withTax(double price) => price * 1.05;
double withoutTax(double price) => price;
double Function(double) priceRuleFor(bool isTaxFree) {
return isTaxFree ? withoutTax : withTax;
}
void main() {
var priceRule = priceRuleFor(false);
print('Notebook: ${priceRule(200)}');
print('Bag: ${priceRule(1000)}');
}Notebook: 210.0
Bag: 1050.0How it works
- The conditional expression picks a function, not a number — no
()after either name. priceRuleFor(false)returnswithTax, andpriceRuleis then used for every item.- Calling
priceRuleFor(true)would change every price without touching theprintlines.
Example 9 — Bank Balance
int deposit(int balance, int amount) => balance + amount;
int withdraw(int balance, int amount) => balance - amount;
int apply(int balance, int amount, int Function(int, int) action) {
int result = action(balance, amount);
print('Balance: $result');
return result;
}
void main() {
int balance = 1000;
balance = apply(balance, 500, deposit);
balance = apply(balance, 300, withdraw);
}Balance: 1500
Balance: 1200How it works
depositandwithdrawshare the shapeint Function(int, int).applyruns whichever action it is given and returns the new balance.mainstores each returned balance and passes it into the next step.
Example 10 — Temperature Conversion
double toFahrenheit(double c) => c * 9 / 5 + 32;
double toKelvin(double c) => c + 273.15;
double Function(double) converterFor(String unit) {
return switch (unit) {
'F' => toFahrenheit,
_ => toKelvin,
};
}
void main() {
String unit = 'F';
double celsius = 30;
print('$celsius°C = ${converterFor(unit)(celsius)}°$unit');
}30.0°C = 86.0°FHow it works
- The switch expression returns a function based on
unit. converterFor(unit)gives backtoFahrenheit; the second(celsius)calls it.30 * 9 / 5 + 32is86.0.
Common Beginner Mistakes
1. Calling the function instead of passing it
// int add(int a, int b) => a + b;
// showResult(4, 2, add(4, 2)); // ✗ passes the number 6Error: The argument type 'int' can't be assigned to the parameter type 'int Function(int, int)'.add(4, 2) runs the function and passes its result. To pass the function, write add.
2. Adding empty () to a tear-off
// showResult(4, 2, add()); // ✗Error: Too few positional arguments: 2 required, 0 given.add() is still a call — just one with missing arguments. A tear-off has no parentheses at
all.
3. A function with the wrong shape
// int square(int n) => n * n;
// int Function(int, int) operation = square; // ✗Error: A value of type 'int Function(int)' can't be assigned to a variable of type 'int Function(int, int)'.The number of parameters, their types, and the return type must all match.
4. Reassigning a var function variable to a different shape
// var greet = (String name) => 'Hi, $name';
// greet = (int n) => 'Number $n'; // ✗Error: A value of type 'String Function(int)' can't be assigned to a variable of type 'String Function(String)'.As in Lecture 005, var fixes the type from the first value — and that includes a function
type.
5. Using the bare type Function
int add(int a, int b) => a + b;
void main() {
Function operation = add; // compiles, but checks nothing
// print(operation('2', '3')); // ✗ crashes at run time
}Unhandled exception:
type 'String' is not a subtype of type 'int' of 'a'Plain Function means "some function", so Dart cannot check the arguments — it behaves like
dynamic. Write the full type, int Function(int, int).
Best Practices
- Pass functions as tear-offs —
add, notadd()or(a, b) => add(a, b). - Write full function types (
int Function(int, int)), never bareFunction. - Use
varfor function variables when the value makes the type obvious. - Give functions you pass around clear names that describe the rule:
tenPercentOff,strictGrade. - Reach for a function parameter when two pieces of code differ only in one step.
- Long function types get hard to read. Lecture 071 shows how a
typedefgives them a short name.
Mini Challenge
Write two functions, celsiusToFahrenheit and fahrenheitToCelsius, each of type
double Function(double). Write void showConversion(double value, double Function(double) convert) that prints <value> -> <result>. Call it once with each function, using 100
both times.
Your program should print exactly:
100.0 -> 212.0
100.0 -> 37.77777777777778Try it yourself before opening the solution.
Show solution
double celsiusToFahrenheit(double c) => c * 9 / 5 + 32;
double fahrenheitToCelsius(double f) => (f - 32) * 5 / 9;
void showConversion(double value, double Function(double) convert) {
print('$value -> ${convert(value)}');
}
void main() {
showConversion(100, celsiusToFahrenheit);
showConversion(100, fahrenheitToCelsius);
}100.0 -> 212.0
100.0 -> 37.77777777777778How it works
- Both converters take a
doubleand return adouble, so both fit the parameter type. - Each call passes a tear-off — no
(). showConversioncalls whichever function it received.
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 cheer(String name)that printsGo, <name>!. Store it in a variable calledshoutand callshout('Sara'). - Write the function type of each:
int half(int n),bool isEmpty(String s),void beep(),double area(double w, double h). - What does each line print, and why are they different?
print(isAdult(20));andprint(isAdult);wherebool isAdult(int age) => age >= 18;
Application
- Declare
int Function(int) operation. Assign(n) => n + 10, printoperation(5), then assign(n) => n * 10and printoperation(5)again. - Write
void runThreeTimes(void Function() action)and use it to printTickthree times. - Write
int calculate(int a, int b, int Function(int, int) op)that returnsop(a, b). Print the result for12and4with anaddtear-off, then with(a, b) => a ~/ b. - Write
String Function(String) formatterFor(bool loud)that returns a function making text upper-case whenloudistrue, and lower-case otherwise. PrintformatterFor(true)('Hello')andformatterFor(false)('Hello'). - This line does not compile. Explain why in one sentence, then fix it:
bool Function(int) check = (String s) => s.isEmpty;
Challenge
- Write
int Function(int, int) pick(String symbol)using a switch expression for'+','-','*'(anything else: a function that returns0). Print9 * 7 = 63usingpick('*'). - Write two bonus rules,
int smallBonus(int marks) => marks + 5;andint bigBonus(int marks) => marks + 10;. Writeint finalMarks(int marks, int Function(int) bonus)that applies the bonus but never returns more than100. Print the result for Ali (88, big bonus) and Zayan (70, small bonus) asAli: 98andZayan: 75, then for Sara (95, big bonus).
Practice Answers
Question 1 — Solution
void cheer(String name) {
print('Go, $name!');
}
void main() {
var shout = cheer;
shout('Sara');
}Go, Sara!Question 2 — Solution
int half(int n)→int Function(int)bool isEmpty(String s)→bool Function(String)void beep()→void Function()double area(double w, double h)→double Function(double, double)
Question 3 — Solution
bool isAdult(int age) => age >= 18;
void main() {
print(isAdult(20));
print(isAdult);
}true
Closure: (int) => bool from Function 'isAdult': static.isAdult(20) calls the function and prints its result. isAdult without () is the
function itself, so Dart prints a description of it.
Question 4 — Solution
void main() {
int Function(int) operation = (n) => n + 10;
print(operation(5));
operation = (n) => n * 10;
print(operation(5));
}15
50Question 5 — Solution
void runThreeTimes(void Function() action) {
action();
action();
action();
}
void tick() {
print('Tick');
}
void main() {
runThreeTimes(tick);
}Tick
Tick
TickQuestion 6 — Solution
int add(int a, int b) => a + b;
int calculate(int a, int b, int Function(int, int) op) {
return op(a, b);
}
void main() {
print(calculate(12, 4, add));
print(calculate(12, 4, (a, b) => a ~/ b));
}16
3Question 7 — Solution
String loudText(String text) => text.toUpperCase();
String quietText(String text) => text.toLowerCase();
String Function(String) formatterFor(bool loud) {
if (loud) {
return loudText;
}
return quietText;
}
void main() {
print(formatterFor(true)('Hello'));
print(formatterFor(false)('Hello'));
}HELLO
helloQuestion 8 — Solution
The variable expects a function that takes an int, but the anonymous function takes a
String. Make the types agree — for example, check an int:
void main() {
bool Function(int) check = (int n) => n == 0;
print(check(0));
}trueQuestion 9 — Solution
int Function(int, int) pick(String symbol) {
return switch (symbol) {
'+' => (a, b) => a + b,
'-' => (a, b) => a - b,
'*' => (a, b) => a * b,
_ => (a, b) => 0,
};
}
void main() {
print('9 * 7 = ${pick('*')(9, 7)}');
}9 * 7 = 63Each arm returns an anonymous function; Dart knows from the return type that a and b
are int.
Question 10 — Solution
int smallBonus(int marks) => marks + 5;
int bigBonus(int marks) => marks + 10;
int finalMarks(int marks, int Function(int) bonus) {
int result = bonus(marks);
if (result > 100) {
return 100;
}
return result;
}
void main() {
print('Ali: ${finalMarks(88, bigBonus)}');
print('Zayan: ${finalMarks(70, smallBonus)}');
print('Sara: ${finalMarks(95, bigBonus)}');
}Ali: 98
Zayan: 75
Sara: 100Sara's 95 + 10 is 105, so the early return caps it at 100.
Quick Revision
- What does it mean that functions are "first-class"?
- What is a tear-off, and how do you write one?
- What is the difference between
addandadd(2, 3)? - What is the function type of
String greet(String name)? - Can a variable of type
int Function(int, int)hold a function that takes oneint? - How do you write a parameter that accepts a function with no parameters and no result?
- In
converterFor('F')(25), what does each pair of parentheses do? - Why is the bare type
Functiona poor choice?
Key Takeaways
- In Dart, functions are values: they can be stored, passed, and returned.
- A tear-off is a function name without
()— it refers to the function without calling it. addis the function;add(2, 3)is its result.- A function type is written
returnType Function(parameterTypes), e.g.int Function(int, int). - Dart checks function types: parameter count, parameter types, and return type must match.
- A parameter with a function type lets the caller choose one step of the work.
- A function can return a function;
f(x)(y)gets a function and then calls it. - Choosing a function with
if,?:, or a switch expression lets a program pick its behaviour at runtime. - Avoid bare
Function— it switches type checking off, likedynamic.
What's Next?
Lecture 036 — Higher-Order Functions. We have just seen that a function can take another function as an argument, or return one. Functions that do this have a name — higher-order functions — and they are one of the most useful tools in Dart. Next we study how to design them, and why so much of Dart is built around them.