Functions in Dart
Learn how to define and use functions in Dart - parameters, named and optional arguments, arrow syntax, closures, recursion, and higher-order functions
Functions are the building blocks of any Dart program. In Dart, functions are first-class objects — they can be assigned to variables, passed as arguments, and returned from other functions. This is a direct consequence of Dart’s multi-paradigm design: it borrows object-oriented structure from Java and C#, but its treatment of functions comes straight from the functional tradition.
What makes Dart’s function model especially interesting is its parameter system. Most languages give you either positional parameters or keyword/named parameters, but Dart gives you both — plus optional positional parameters, default values, and required named parameters. Combined with sound null safety, this lets you write APIs that are both flexible and safe at compile time.
In this tutorial you’ll learn how to define functions, work with Dart’s three kinds of parameters, use the concise arrow syntax, capture state with closures, write recursive functions, and treat functions as values with higher-order functions. By the end you’ll understand why Dart code — and especially Flutter code — relies so heavily on passing functions around.
Defining and Calling Functions
A Dart function declares its return type, a name, a parameter list, and a body. If a function does not return a value, its return type is void. Dart also infers return types, but being explicit is idiomatic for top-level functions.
Create a file named functions_basic.dart:
| |
Functions can be called anywhere they are in scope, and their results can be nested directly inside other expressions, as square(square(3)) shows.
Arrow Functions and Parameters
For functions whose body is a single expression, Dart offers the => “arrow” syntax — => expr is shorthand for { return expr; }. Dart also supports three styles of parameters:
- Required positional — the default; order matters.
- Optional positional — wrapped in
[ ], may be omitted. - Named — wrapped in
{ }, passed by name; mark themrequiredor give them a default.
Create a file named functions_params.dart:
| |
Named parameters make call sites self-documenting — price(base: 100.0, taxRate: 0.08) reads clearly without checking the function signature. This is why Flutter widget constructors use named parameters almost everywhere.
Scope, Closures, and Nested Functions
Dart uses lexical scoping: an inner function can see variables declared in the functions that enclose it. When a nested function captures and remembers those variables even after the outer function returns, it forms a closure. Because functions are objects, a closure can be returned and called later.
Create a file named functions_closures.dart:
| |
The type int Function() is the type of “a function that takes no arguments and returns an int” — Dart has full first-class function types. Notice that counterA and counterB each carry their own count; closures capture variables, not values.
Recursion
A recursive function calls itself to solve a problem in terms of smaller subproblems. Dart handles recursion like any C-style language, using the call stack. The classic example is factorial, along with the Fibonacci sequence.
Create a file named functions_recursion.dart:
| |
Each call to factorial waits for the smaller call to finish before multiplying, until the base case n <= 1 stops the recursion.
Higher-Order Functions
Because functions are first-class objects, they can be passed as arguments and returned as results. Functions that do this are called higher-order functions. Dart’s collection types lean heavily on them through methods like map, where, and reduce, and you can also pass anonymous (lambda) functions inline.
Create a file named functions_higher_order.dart:
| |
The (n) => n * n syntax is an anonymous function — a function with no name passed directly where a value is expected. This style is everywhere in idiomatic Dart, from list processing to Flutter event callbacks.
Running with Docker
You can run every example with the official Dart image — no local SDK required.
| |
Expected Output
Running functions_basic.dart:
=== Functions ===
square(5) = 25
square(square(3)) = 81
Running functions_params.dart:
cube(3) = 27
Hello, World!
Welcome, Dart!
Total: 108.0
Total: 60.0
Running functions_closures.dart:
A: 1
A: 2
A: 3
B: 1
The secret is 42
Running functions_recursion.dart:
5! = 120
10! = 3628800
Fibonacci: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Running functions_higher_order.dart:
Doubled: [2, 4, 6, 8, 10]
Squared: [1, 4, 9, 16, 25]
Tripled: [3, 6, 9, 12, 15]
Evens: [2, 4]
Sum: 15
Key Concepts
- Functions are first-class objects — they can be stored in variables, passed as arguments, and returned, with types written as
ReturnType Function(ArgTypes). - Arrow syntax —
=> expris concise shorthand for a single-expression body that returnsexpr. - Three parameter styles — required positional, optional positional in
[ ], and named in{ }; named parameters can berequiredor carry default values. - Named parameters self-document call sites, which is why Flutter constructors rely on them so heavily.
- Closures capture variables, not values — each closure keeps its own live reference to the enclosing scope’s variables.
- Recursion works through the call stack and needs a base case to terminate; factorial and Fibonacci are canonical examples.
- Higher-order functions power Dart’s collection pipeline (
map,where,reduce) and its callback-driven UI code. - Anonymous functions —
(args) => bodyor(args) { ... }— let you define behavior inline exactly where it’s needed.
Comments
Loading comments...
Leave a Comment