Lesson 14 min

Arrow Functions & Lambdas

00:00 / 00:00

Arrow Functions & Lambdas

In Dart, functions are first-class citizens — you can assign them to variables, pass them as arguments, and return them from other functions. Arrow functions and anonymous functions (lambdas) make working with functions concise and expressive.


1. Arrow Functions — The => Syntax

An arrow function is a shorthand for a function that contains only a single expression. Instead of writing a full function body with {} and return, you use =>.

Syntax:

dart
returnType functionName(params) => expression;

Regular Function vs Arrow Function

dart
// ✅ Regular function int add(int a, int b) { return a + b; } // ✅ Arrow function — same thing, fewer lines int add(int a, int b) => a + b; void main() { print(add(3, 4)); // 7 }

More Examples

dart
// Returns a greeting string String greet(String name) => 'Hello, $name!'; // Checks if a number is even bool isEven(int n) => n % 2 == 0; // Prints a message (void arrow function) void sayHi() => print('Hi there!'); void main() { print(greet('Rahul')); // Hello, Rahul! print(isEven(10)); // true sayHi(); // Hi there! }

When to use =>: Only when the function body is a single expression. If you need multiple statements, if-else blocks, or loops — use the regular {} syntax.


2. Anonymous Functions (Function Literals / Lambdas)

An anonymous function is a function without a name. It is defined inline and often passed directly as an argument or assigned to a variable.

dart
// Named function int square(int x) { return x * x; } // Anonymous function assigned to a variable var square = (int x) { return x * x; }; // Arrow-style anonymous function var square = (int x) => x * x; void main() { print(square(5)); // 25 }

Anonymous Functions as Arguments

Anonymous functions shine when passed directly into other functions:

dart
void main() { List<String> fruits = ['mango', 'apple', 'banana']; // Passing an anonymous function to forEach fruits.forEach((fruit) { print('I love $fruit'); }); // Arrow-style — even more concise fruits.forEach((fruit) => print('Fruit: $fruit')); }

Output:

I love mango
I love apple
I love banana
Fruit: mango
Fruit: apple
Fruit: banana

3. Closures — Capturing Variables from Outer Scope

A closure is a function that "closes over" variables from its surrounding scope — it remembers and can access those variables even after the outer function has finished executing.

dart
Function makeCounter() { int count = 0; // outer variable return () { count++; // inner function captures 'count' print('Count: $count'); }; } void main() { var counter = makeCounter(); counter(); // Count: 1 counter(); // Count: 2 counter(); // Count: 3 // A new, independent counter var counter2 = makeCounter(); counter2(); // Count: 1 }

Each call to makeCounter() creates a new closure with its own count variable.

Practical Closure Example — Multiplier Factory

dart
Function multiplier(int factor) { return (int number) => number * factor; } void main() { var doubleIt = multiplier(2); var tripleIt = multiplier(3); print(doubleIt(5)); // 10 print(tripleIt(5)); // 15 print(doubleIt(8)); // 16 }

4. Assigning Functions to Variables — Function Type

In Dart, the Function type lets you store any function in a variable:

dart
void main() { // Using 'Function' type (loose typing) Function greet = (String name) => 'Hello, $name!'; print(greet('Priya')); // Hello, Priya! // More specific type annotation String Function(String) greet2 = (name) => 'Hi, $name!'; print(greet2('Amit')); // Hi, Amit! // Function that takes int, returns bool bool Function(int) isPositive = (n) => n > 0; print(isPositive(5)); // true print(isPositive(-3)); // false }

Passing Functions as Arguments

dart
void applyOperation(int a, int b, int Function(int, int) operation) { int result = operation(a, b); print('Result: $result'); } void main() { applyOperation(10, 5, (a, b) => a + b); // Result: 15 applyOperation(10, 5, (a, b) => a * b); // Result: 50 applyOperation(10, 5, (a, b) => a - b); // Result: 5 }

5. typedef — Function Type Aliases

typedef lets you create a named alias for a function signature, making your code more readable and reusable.

dart
// Define type aliases for function signatures typedef MathOperation = int Function(int, int); typedef Validator = bool Function(String); typedef Callback = void Function(String message); // Use typedef as a parameter type void calculate(int a, int b, MathOperation op) { print('Result: ${op(a, b)}'); } bool validateEmail(String email, Validator validator) { return validator(email); } void main() { MathOperation add = (a, b) => a + b; MathOperation multiply = (a, b) => a * b; calculate(4, 3, add); // Result: 7 calculate(4, 3, multiply); // Result: 12 Validator emailCheck = (email) => email.contains('@'); print(validateEmail('user@example.com', emailCheck)); // true print(validateEmail('notanemail', emailCheck)); // false }

Tip: Use typedef when you reuse the same function signature in multiple places. It acts like a contract — anyone using the typedef knows exactly what arguments and return type to expect.


6. Practical Examples

Sorting with a Comparator

dart
void main() { List<String> names = ['Zara', 'Amit', 'Priya', 'Rahul']; // Sort alphabetically names.sort((a, b) => a.compareTo(b)); print(names); // [Amit, Priya, Rahul, Zara] // Sort by string length names.sort((a, b) => a.length.compareTo(b.length)); print(names); // [Amit, Zara, Priya, Rahul] List<int> scores = [45, 92, 78, 56, 88]; // Sort descending scores.sort((a, b) => b.compareTo(a)); print(scores); // [92, 88, 78, 56, 45] }

Callbacks

dart
typedef OnComplete = void Function(String result); void fetchData(String url, OnComplete onComplete) { print('Fetching from $url...'); String data = '{"name": "Dart", "version": "3.0"}'; onComplete(data); } void main() { fetchData('https://api.example.com/data', (result) { print('Data received: $result'); }); }

Output:

Fetching from https://api.example.com/data...
Data received: {"name": "Dart", "version": "3.0"}

Converting Regular Functions to Arrow Syntax

dart
// Before — regular functions bool isAdult(int age) { return age >= 18; } String formatName(String first, String last) { return '$first $last'; } double calculateArea(double radius) { return 3.14159 * radius * radius; } // After — arrow functions (cleaner!) bool isAdult(int age) => age >= 18; String formatName(String first, String last) => '$first $last'; double calculateArea(double radius) => 3.14159 * radius * radius; void main() { print(isAdult(20)); // true print(isAdult(15)); // false print(formatName('Raj', 'Shah')); // Raj Shah print(calculateArea(5.0)); // 78.53975 }

Summary

ConceptDescriptionExample
Arrow Function =>Single-expression shorthandint add(a, b) => a + b;
Anonymous FunctionNameless function literal(x) => x * 2
ClosureFunction capturing outer variablesmakeCounter() pattern
Function typeStoring functions in variablesFunction fn = () => ...;
typedefNamed alias for function signaturetypedef Op = int Function(int, int);

Arrow functions and anonymous functions are everywhere in Dart — especially when working with collections, callbacks, and Flutter event handlers. Mastering them is key to writing clean, idiomatic Dart code.

WhatsApp