Lesson 22 min

Higher Order Functions & Closures

00:00 / 00:00

Higher Order Functions & Closures

One of Dart's most powerful features is treating functions as first-class citizens. This means you can pass functions as arguments, return them from other functions, and assign them to variables — just like you do with integers or strings.


1. Functions as First-Class Citizens

dart
void greet(String name) => print('Hello, $name!'); void main() { // Assign a function to a variable var myFunction = greet; myFunction('Rahul'); // Hello, Rahul! // Store functions in a list List<Function> actions = [ () => print('Action 1'), () => print('Action 2'), () => print('Action 3'), ]; actions.forEach((action) => action()); }

2. Higher Order Functions

A higher-order function is any function that:

  • Takes one or more functions as parameters, OR
  • Returns a function as its result
dart
// Takes a function as a parameter void doTwice(Function action) { action(); action(); } // Returns a function Function adder(int x) { return (int y) => x + y; } void main() { doTwice(() => print('Hello!')); // Hello! // Hello! var add5 = adder(5); print(add5(3)); // 8 print(add5(10)); // 15 }

3. Collection Higher-Order Functions

Dart's List, Set, and Iterable classes come with powerful built-in higher-order functions. These are the backbone of functional-style Dart programming.

Let's explore each one with a real-world example — a student grade system.

dart
void main() { List<int> grades = [85, 92, 56, 73, 90, 47, 68, 95]; List<String> students = ['Amit', 'Priya', 'Rahul', 'Zara', 'Dev', 'Nisha', 'Karan', 'Meera']; }

3.1 map() — Transform Each Element

map() applies a function to every element and returns a new Iterable.

dart
void main() { List<int> grades = [85, 92, 56, 73, 90]; // Add 5 bonus marks to each student var bonusGrades = grades.map((grade) => grade + 5).toList(); print(bonusGrades); // [90, 97, 61, 78, 95] // Convert grades to letter grades var letterGrades = grades.map((grade) { if (grade >= 90) return 'A'; if (grade >= 75) return 'B'; if (grade >= 60) return 'C'; return 'F'; }).toList(); print(letterGrades); // [B, A, F, B, A] // String transformation example List<String> names = ['amit', 'priya', 'rahul']; var uppercased = names.map((name) => name.toUpperCase()).toList(); print(uppercased); // [AMIT, PRIYA, RAHUL] }

3.2 where() — Filter Elements (like filter in JavaScript)

where() returns only the elements that satisfy a condition.

dart
void main() { List<int> grades = [85, 92, 56, 73, 90, 47, 68, 95]; // Students who passed (>= 60) var passed = grades.where((grade) => grade >= 60).toList(); print(passed); // [85, 92, 73, 90, 68, 95] // Students who failed var failed = grades.where((grade) => grade < 60).toList(); print(failed); // [56, 47] // Distinction holders (>= 90) var distinction = grades.where((grade) => grade >= 90).toList(); print(distinction); // [92, 90, 95] }

3.3 reduce() — Collapse to a Single Value

reduce() combines all elements using a function, starting from the first element. It does not accept an initial value.

dart
void main() { List<int> grades = [85, 92, 56, 73, 90]; // Find total marks int total = grades.reduce((sum, grade) => sum + grade); print('Total: $total'); // Total: 396 // Find maximum grade int highest = grades.reduce((max, grade) => grade > max ? grade : max); print('Highest: $highest'); // Highest: 92 // Find minimum grade int lowest = grades.reduce((min, grade) => grade < min ? grade : min); print('Lowest: $lowest'); // Lowest: 56 }

Warning: reduce() throws an error on an empty list. Use fold() when the list might be empty.


3.4 fold() — Like reduce() with an Initial Value

fold() is like reduce() but you provide a starting/initial value. Safe on empty lists.

dart
void main() { List<int> grades = [85, 92, 56, 73, 90]; // Calculate total with initial value of 0 int total = grades.fold(0, (sum, grade) => sum + grade); print('Total: $total'); // Total: 396 // Average calculation double average = grades.fold(0, (sum, grade) => sum + grade) / grades.length; print('Average: $average'); // Average: 79.2 // Build a summary string using fold String summary = grades.fold('Grades: ', (str, grade) => '$str$grade '); print(summary); // Grades: 85 92 56 73 90 // Works on empty list too List<int> empty = []; int result = empty.fold(0, (sum, x) => sum + x); print(result); // 0 (no crash!) }

3.5 any() — Returns true if Any Element Matches

dart
void main() { List<int> grades = [85, 92, 56, 73, 90, 47]; bool anyFailed = grades.any((grade) => grade < 60); print('Anyone failed? $anyFailed'); // Anyone failed? true bool anyDistinction = grades.any((grade) => grade >= 95); print('Anyone got distinction? $anyDistinction'); // Anyone got distinction? false List<String> fruits = ['apple', 'mango', 'banana']; bool hasMango = fruits.any((fruit) => fruit == 'mango'); print('Has mango? $hasMango'); // Has mango? true }

3.6 every() — Returns true if All Elements Match

dart
void main() { List<int> grades = [85, 92, 73, 90]; bool allPassed = grades.every((grade) => grade >= 60); print('All passed? $allPassed'); // All passed? true bool allDistinction = grades.every((grade) => grade >= 90); print('All distinction? $allDistinction'); // All distinction? false List<int> evenNumbers = [2, 4, 6, 8, 10]; bool allEven = evenNumbers.every((n) => n % 2 == 0); print('All even? $allEven'); // All even? true }

3.7 forEach() — Iterate with Side Effects

forEach() runs a function on each element. It returns void — use it when you want to perform actions (print, update UI, etc.) rather than transform data.

dart
void main() { List<String> students = ['Amit', 'Priya', 'Rahul']; List<int> grades = [85, 92, 56]; // Print each student's info for (int i = 0; i < students.length; i++) { print('${students[i]}: ${grades[i]}'); } // Using forEach students.forEach((student) { print('Student: $student'); }); // Arrow style grades.forEach((grade) => print('Grade: $grade')); }

Note: Prefer for-in loops when you need await inside the body, or when you need break/continue. Use forEach for simple, non-async iterations.


3.8 toList() and toSet() — Convert Back to Collection

Most HOFs return an Iterable, not a List. Use toList() or toSet() to convert:

dart
void main() { List<int> numbers = [1, 2, 2, 3, 3, 4, 5]; // map/where return Iterable — call .toList() List<int> doubled = numbers.map((n) => n * 2).toList(); print(doubled); // [2, 4, 4, 6, 6, 8, 10] // .toSet() removes duplicates Set<int> uniqueDoubled = numbers.map((n) => n * 2).toSet(); print(uniqueDoubled); // {2, 4, 6, 8, 10} // Filter then convert List<int> evens = numbers.where((n) => n % 2 == 0).toList(); print(evens); // [2, 2, 4] }

4. Method Chaining

The real power comes from chaining multiple HOFs together to build expressive data pipelines:

dart
void main() { List<int> grades = [85, 92, 56, 73, 90, 47, 68, 95]; // Find the average of passing grades only var passingGrades = grades .where((g) => g >= 60) // filter: keep passing grades .map((g) => g + 2) // transform: add 2 grace marks .toList(); double average = passingGrades.fold(0, (sum, g) => sum + g) / passingGrades.length; print('Average of passing grades (with grace): $average'); // Average of passing grades (with grace): 85.83... // Count failed students int failCount = grades.where((g) => g < 60).length; print('Failed count: $failCount'); // Failed count: 2 // Get top 3 scorers List<int> sorted = [...grades]..sort((a, b) => b.compareTo(a)); List<int> top3 = sorted.take(3).toList(); print('Top 3: $top3'); // Top 3: [95, 92, 90] }

5. Real Project Example — Product Filtering & Price Calculation

dart
class Product { final String name; final String category; final double price; final bool inStock; Product(this.name, this.category, this.price, this.inStock); } void main() { List<Product> products = [ Product('Laptop', 'Electronics', 75000, true), Product('Phone', 'Electronics', 25000, false), Product('Shirt', 'Clothing', 999, true), Product('Headphones', 'Electronics', 5000, true), Product('Jeans', 'Clothing', 1999, true), Product('Tablet', 'Electronics', 35000, false), ]; // 1. Get all in-stock electronics var availableElectronics = products .where((p) => p.category == 'Electronics' && p.inStock) .toList(); print('Available Electronics:'); availableElectronics.forEach((p) => print(' ${p.name}: Rs.${p.price}')); // 2. Total price of in-stock items double total = products .where((p) => p.inStock) .fold(0.0, (sum, p) => sum + p.price); print('\nTotal in-stock value: Rs.$total'); // 3. Get product names sorted by price var sortedNames = products .where((p) => p.inStock) .toList() ..sort((a, b) => a.price.compareTo(b.price)); print('\nProducts by price (low to high):'); sortedNames.forEach((p) => print(' ${p.name}: Rs.${p.price}')); // 4. Check if any item is out of stock bool anyOutOfStock = products.any((p) => !p.inStock); print('\nAny out of stock? $anyOutOfStock'); // true // 5. All clothing available? bool allClothingAvailable = products .where((p) => p.category == 'Clothing') .every((p) => p.inStock); print('All clothing in stock? $allClothingAvailable'); // true }

Summary

FunctionPurposeReturns
map()Transform each elementIterable
where()Filter elements by conditionIterable
reduce()Collapse to one value (no initial)Single value
fold()Collapse with initial valueSingle value
any()True if at least one matchesbool
every()True if all elements matchbool
forEach()Side effects on each elementvoid
toList()Convert Iterable to ListList
toSet()Convert Iterable to Set (unique)Set

Higher-order functions are at the heart of modern Dart and Flutter development. They let you write declarative, readable, and chainable data processing code — replacing complex loops with expressive one-liners.

WhatsApp