Higher Order Functions & Closures
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
dartvoid 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.
dartvoid 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.
dartvoid 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.
dartvoid 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.
dartvoid 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.
dartvoid 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
dartvoid 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
dartvoid 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.
dartvoid 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:
dartvoid 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:
dartvoid 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
dartclass 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
| Function | Purpose | Returns |
|---|---|---|
map() | Transform each element | Iterable |
where() | Filter elements by condition | Iterable |
reduce() | Collapse to one value (no initial) | Single value |
fold() | Collapse with initial value | Single value |
any() | True if at least one matches | bool |
every() | True if all elements match | bool |
forEach() | Side effects on each element | void |
toList() | Convert Iterable to List | List |
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.