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


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.

DART
// 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',
);
TEXT 📖 Display only
> **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


3. Function Declarations and Arrow Functions

(1) Function Declaration Syntax

▶ Example

TEXT 📖 Display only
> **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

DART
// 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);
}
TEXT 📖 Display only
> **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

100%
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"]
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
// 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
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
// 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',
  );
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
// 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
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
// 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)
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
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
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
// 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
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
void main() {
  final orders = ['ORD-001', 'ORD-002', 'ORD-003'];
  orders.forEach((order) => print('Processing: $order'));
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
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]
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
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
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
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
}
TEXT 📖 Display only
> **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

TEXT 📖 Display only
> **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

DART
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
}
TEXT 📖 Display only
> **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

DART
// ============================================
// 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);
}
TEXT 📖 Display only
> **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:

TEXT 📖 Display only
=== 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, while void 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 reduce and fold? A: reduce doesn't require an initial value, but the collection cannot be empty, and its return type matches the element type. fold requires an initial value, can work on an empty collection, and its return type can be different. fold is 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/where return a List or an Iterable? A: They return an Iterable (lazy evaluation). Add .toList() if you need a List. Lazy evaluation means the map/where chain 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 typedef used for? A: typedef creates an alias for a function type, making code more readable. For example, typedef Validator = bool Function(String); is clearer than bool Function(String).


📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty ⭐): Write a formatUSD function 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").
  2. Intermediate Exercise (Difficulty ⭐⭐): Use a closure to implement a makeDiscountCalculator factory 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.
  3. Challenge Exercise (Difficulty ⭐⭐⭐): Using a chain of map/where/fold calls 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 any for loops.

← Previous Lesson | Next Lesson →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏