Dart: Dart Functions — First-Class Citizens Closures
Last updated: 2026-08-26
Functions are the building blocks of code — small functions combine to build large systems.
1. What You Will Learn
- Function declarations and arrow functions (
=>syntax) - Optional parameters: Named parameters
{}and positional parameters[] - The
requiredparameter marker - Closures and the variable capture mechanism
- Higher-order functions: forEach / map / where / reduce / fold
2. A Developer's True Story
(1) The Pain Point: Frequent Invocation Errors Due to Parameter Confusion
Bob defined a processOrder function in DataPipeline with 7 positional parameters. Team members often mixed up the parameter order when calling it — someone passed taxRate as discountRate, causing all 50,000 orders in a batch to have incorrect discount calculations. Customer complaints surged, and emergency fixes took 2 days.
(2) The Solution: Named Parameters
Dart's named parameters {} give each parameter a clear label, eliminating order dependency during invocation. The compiler can also check if required parameters are provided.
// Before: positional params - easy to mix up
// processOrder('ORD-001', 1500.0, 0.08, 0.1, true, 'USD', 'json');
// After: named params - self-documenting
processOrder(
id: 'ORD-001',
amount: 1500.0,
taxRate: 0.08,
discountRate: 0.1,
currency: 'USD',
);
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
(3) The Benefits
- Named parameters make function calls self-documenting, eliminating the need to check the definition to understand parameter meanings.
- The
requiredmarker lets the compiler help you check mandatory parameters. - Higher-order functions make collection operations more concise, reducing for-loop usage by 70%.
3. Function Declarations and Arrow Functions
(1) Function Declaration Syntax
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: Function declaration methods
// Standard function declaration
double calculateTax(double amount, double taxRate) {
return amount * taxRate;
}
// Arrow function (expression body)
double calculateTaxShort(double amount, double taxRate) =>
amount * taxRate;
// Void function
void printOrderSummary(String id, double amount) {
print('Order $id: \$${amount.toStringAsFixed(2)} USD');
}
// Main entry
void main() {
print(calculateTax(1500.0, 0.08)); // 120.0
print(calculateTaxShort(1500.0, 0.08)); // 120.0
printOrderSummary('ORD-001', 1500.0);
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
| Form | Syntax | Use Case |
|---|---|---|
| Standard Function | { return expr; } |
Multiple statements |
| Arrow Function | => expr |
Single expression return |
| Void Function | void name() {} |
No return value |
4. Detailed Parameter Types
(1) Parameter Decision Tree
graph TD A[Function Params] --> B[Positional<br/>required by default] A --> C[Named<br/>optional by default] B --> B1[Required positional] B --> B2["Optional positional []"] C --> C1["Required named required"] C --> C2["Optional named with default"]
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: Positional parameters
// Required positional parameters
double calculateTotal(double amount, double taxRate) {
return amount * (1 + taxRate);
}
// Optional positional parameters
String formatCurrency(double amount, [String currency = 'USD']) {
return '\$${amount.toStringAsFixed(2)} $currency';
}
void main() {
print(calculateTotal(1500.0, 0.08)); // 1620.0
print(formatCurrency(1500.0)); // $1500.00 USD
print(formatCurrency(1500.0, 'EUR')); // $1500.00 EUR
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: Named parameters
// Named parameters - optional by default
double processOrder({
required String id,
required double amount,
double taxRate = 0.08,
double discountRate = 0,
String currency = 'USD',
}) {
final discounted = amount * (1 - discountRate);
final taxed = discounted * (1 + taxRate);
print('Order $id: \$${taxed.toStringAsFixed(2)} $currency');
return taxed;
}
void main() {
processOrder(id: 'ORD-001', amount: 1500.0);
processOrder(
id: 'ORD-002',
amount: 3200.0,
taxRate: 0.10,
discountRate: 0.15,
currency: 'EUR',
);
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: The required marker
// required forces the caller to provide the parameter
class OrderValidator {
bool validate({
required String orderId,
required double amount,
String? customerName, // Optional - can be null
}) {
if (orderId.isEmpty) return false;
if (amount <= 0) return false;
return true;
}
}
void main() {
final validator = OrderValidator();
// validator.validate(orderId: 'ORD-001'); // Error! amount is required
validator.validate(orderId: 'ORD-001', amount: 1500.0); // OK
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
| Parameter Type | Syntax | Required by Default | Default Value |
|---|---|---|---|
| Positional | Type name |
Yes | None |
| Optional Positional | [Type name = default] |
No | Can be specified |
| Named | {Type name} |
No | null |
| Required Named | {required Type name} |
Yes | None |
| Named with Default | {Type name = default} |
No | Specified value |
5. Closures and Variable Capture
(1) Closure Principles
A closure is a function object that can access variables from its lexical scope, even when called outside that scope.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: Closure basics
// Function that returns a function (closure)
Function makeTaxCalculator(double taxRate) {
// taxRate is captured by the returned function
return (double amount) => amount * taxRate;
}
void main() {
final usTax = makeTaxCalculator(0.08);
final euTax = makeTaxCalculator(0.20);
print(usTax(1500.0)); // 120.0 (8% tax)
print(euTax(1500.0)); // 300.0 (20% tax)
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: Closure capturing variables
void main() {
// Counter closure
int Function() makeCounter() {
int count = 0; // Captured variable
return () => ++count;
}
final counterA = makeCounter();
final counterB = makeCounter();
print(counterA()); // 1
print(counterA()); // 2
print(counterB()); // 1 (separate captured variable)
print(counterA()); // 3
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: Practical application of closures
// DataPipeline filter factory
typedef OrderFilter = bool Function(Map<String, dynamic> order);
OrderFilter makeAmountFilter(double minAmount, {double? maxAmount}) {
return (order) {
final amount = order['amount'] as double;
if (amount < minAmount) return false;
if (maxAmount != null && amount > maxAmount) return false;
return true;
};
}
void main() {
final orders = [
{'id': 'ORD-001', 'amount': 1500.0},
{'id': 'ORD-002', 'amount': 50.0},
{'id': 'ORD-003', 'amount': 3200.0},
];
final premiumFilter = makeAmountFilter(1000);
final midRangeFilter = makeAmountFilter(100, maxAmount: 1000);
print(orders.where(premiumFilter).length); // 2
print(orders.where(midRangeFilter).length); // 1
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
6. Higher-Order Functions
(1) Collection Operation Higher-Order Functions
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: forEach
void main() {
final orders = ['ORD-001', 'ORD-002', 'ORD-003'];
orders.forEach((order) => print('Processing: $order'));
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: map — Transformation
void main() {
final amounts = [1500.0, 3200.0, 890.0];
// Transform amounts to formatted strings
final formatted = amounts.map((a) => '\$${a.toStringAsFixed(2)} USD').toList();
print(formatted); // [$1500.00 USD, $3200.00 USD, $890.00 USD]
// Apply tax calculation
final withTax = amounts.map((a) => a * 1.08).toList();
print(withTax); // [1620.0, 3456.0, 961.2]
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: where — Filtering
void main() {
final orders = [
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed'},
{'id': 'ORD-002', 'amount': 50.0, 'status': 'completed'},
{'id': 'ORD-003', 'amount': 3200.0, 'status': 'pending'},
];
// Filter high-value completed orders
final premiumCompleted = orders
.where((o) => o['status'] == 'completed')
.where((o) => (o['amount'] as double) >= 1000)
.toList();
print('Premium completed: ${premiumCompleted.length}'); // 1
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: reduce — Aggregation
void main() {
final amounts = [1500.0, 3200.0, 890.0];
// Sum all amounts
final total = amounts.reduce((sum, amount) => sum + amount);
print('Total: \$${total.toStringAsFixed(2)} USD'); // $5590.00 USD
// Find maximum
final maxAmount = amounts.reduce((max, amount) => amount > max ? amount : max);
print('Max: \$${maxAmount.toStringAsFixed(2)} USD'); // $3200.00 USD
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
: fold — Aggregation with an initial value
void main() {
final orders = [
{'amount': 1500.0, 'status': 'completed'},
{'amount': 3200.0, 'status': 'pending'},
{'amount': 890.0, 'status': 'completed'},
];
// fold with initial value and accumulator
final completedRevenue = orders.fold``<double>``(0.0, (sum, order) {
if (order['status'] == 'completed') {
return sum + (order['amount'] as double);
}
return sum;
});
print('Completed revenue: \$${completedRevenue.toStringAsFixed(2)} USD');
// Completed revenue: $2390.00 USD
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
| Function | Purpose | Return Type | Needs Initial Value |
|---|---|---|---|
forEach |
Iterate and execute | void | No |
map |
Transform each element | Iterable<T> |
No |
where |
Filter elements | Iterable<T> |
No |
reduce |
Aggregate to single value | T | No (but collection cannot be empty) |
fold |
Aggregate with initial value | Any | Yes |
7. Complete Example: DataPipeline Order Analysis Function Library
// ============================================
// DataPipeline Order Analysis Function Library
// Demonstrates functions, closures, and higher-order functions
// ============================================
typedef Order = Map<String, dynamic>;
// Filter factory using closures
bool Function(Order) makeFilter({
double? minAmount,
String? requiredStatus,
}) {
return (Order order) {
if (minAmount != null && (order['amount'] as double) < minAmount) {
return false;
}
if (requiredStatus != null && order['status'] != requiredStatus) {
return false;
}
return true;
};
}
// Aggregation function with fold
Map<String, double> aggregateByCategory(List``<Order>`` orders) {
return orders.fold<Map<String, double>>({}, (acc, order) {
final category = (order['category'] ?? 'Uncategorized') as String;
final amount = order['amount'] as double;
acc[category] = (acc[category] ?? 0) + amount;
return acc;
});
}
// Format report entry
String formatEntry(String category, double amount) =>
' $category: \$${amount.toStringAsFixed(2)} USD';
// Main pipeline function
void runPipeline(List``<Order>`` orders, {double minAmount = 0}) {
final filter = makeFilter(minAmount: minAmount, requiredStatus: 'completed');
final filtered = orders.where(filter).toList();
final total = filtered.fold``<double>``(
0, (sum, o) => sum + (o['amount'] as double));
final byCategory = aggregateByCategory(filtered);
print('=== DataPipeline Report ===');
print('Total orders: ${orders.length}');
print('Filtered: ${filtered.length} (min: \$${minAmount} USD)');
print('Revenue: \$${total.toStringAsFixed(2)} USD');
print('\nBy Category:');
byCategory.forEach((cat, amount) => print(formatEntry(cat, amount)));
}
void main() {
final orders = ``<Order>``[
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed', 'category': 'Electronics'},
{'id': 'ORD-002', 'amount': 50.0, 'status': 'completed', 'category': 'Books'},
{'id': 'ORD-003', 'amount': 3200.0, 'status': 'pending', 'category': 'Electronics'},
{'id': 'ORD-004', 'amount': 890.0, 'status': 'completed', 'category': 'Clothing'},
{'id': 'ORD-005', 'amount': 2100.0, 'status': 'completed', 'category': 'Electronics'},
];
runPipeline(orders, minAmount: 100);
}
> **Output:** Run locally in DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and results may vary slightly depending on the SDK version.
Output:
=== DataPipeline Report ===
Total orders: 5
Filtered: 3 (min: $100 USD)
Revenue: $4490.00 USD
By Category:
Electronics: $3600.00 USD
Clothing: $890.00 USD
❓ FAQ
Q: Can named and positional parameters be mixed? A: Yes, but named parameters must come after positional parameters. For example,
void f(int a, {int? b})is valid, whilevoid f({int? b}, int a)is invalid.
Q: Is there a performance difference between arrow functions and regular functions? A: No. Arrow functions are just syntactic sugar; they compile to the exact same code as regular functions. The choice depends on code readability.
Q: What's the difference between
reduceandfold? A:reducedoesn't require an initial value, but the collection cannot be empty, and its return type matches the element type.foldrequires an initial value, can work on an empty collection, and its return type can be different.foldis generally recommended.
Q: Does a closure capture variables by value or by reference? A: Dart closures capture variables by reference (not a copy of the value). Therefore, modifying the variable inside the closure affects the external variable, and external modifications affect the closure.
Q: Do
map/wherereturn aListor anIterable? A: They return anIterable(lazy evaluation). Add.toList()if you need aList. Lazy evaluation means themap/wherechain is only computed when iterated, without creating intermediate collections.
Q: Can functions be passed as arguments to any function? A: Yes, Dart functions are first-class citizens. They can be assigned to variables, passed as arguments, and returned as values.
Q: What is
typedefused for? A:typedefcreates an alias for a function type, making code more readable. For example,typedef Validator = bool Function(String);is clearer thanbool Function(String).
📖 Summary
- Dart functions are first-class citizens: they can be assigned, passed as arguments, and returned.
- Named parameters +
requiredmake function calls self-documenting and prevent parameter confusion. - Closures capture references to external variables, enabling factory functions and configurable filters.
- Higher-order functions
map/where/reduce/foldare core tools for collection operations. - Function parameter decision: use
requirednamed parameters for mandatory ones, and named parameters with defaults for optional ones.
📝 Exercises
- Basic Exercise (Difficulty ⭐): Write a
formatUSDfunction that uses named parameters to accept an amount and currency symbol, returning a formatted string (e.g.,formatUSD(amount: 1500.5, symbol: '€')returns"€1,500.50"). - Intermediate Exercise (Difficulty ⭐⭐): Use a closure to implement a
makeDiscountCalculatorfactory function that accepts a discount rate parameter and returns a function to calculate the discounted price. Create two calculators with different discount rates and compare the results. - Challenge Exercise (Difficulty ⭐⭐⭐): Using a chain of
map/where/foldcalls on a set of order data: filter completed orders → group by category → calculate total revenue per category → find the category with the highest revenue. Do this entire process without using anyforloops.