flutter
/

Dart Closures – Capturing Variables with Anonymous Functions

Last Sync: Today

On this page

8
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Dart Closures – Capturing Variables with Anonymous Functions

What is a Closure?

A closure is a function that captures variables from its surrounding lexical scope. In other words, a closure 'remembers' the environment in which it was created, even after that environment is no longer active. This allows the function to access and modify those captured variables, creating functions with private state.

In Dart, every function is a closure because functions can access variables defined in their outer scope. However, the term is often used specifically for anonymous functions that outlive the scope where they were defined (e.g., when returned from another function).

Lexical Scoping

Before understanding closures, you need to understand lexical scoping. In Dart, the scope of a variable is determined by where it is declared in the code. Inner functions can access variables from outer functions.

DARTRead-only
1
void outerFunction() {
  int outerVar = 10;

  void innerFunction() {
    // innerFunction can access outerVar
    print(outerVar); // 10
  }

  innerFunction();
}

Creating a Simple Closure

When you return an inner function, it continues to have access to the outer function's variables, even after the outer function has finished executing. That returned function is a closure.

DARTRead-only
1
Function makeCounter() {
  int count = 0; // captured variable

  return () {
    count++; // access and modify captured variable
    return count;
  };
}

void main() {
  var counter = makeCounter();
  print(counter()); // 1
  print(counter()); // 2
  print(counter()); // 3

  var anotherCounter = makeCounter();
  print(anotherCounter()); // 1 (separate closure, separate state)
}

Each call to makeCounter creates a new closure with its own count variable. The closures keep the variable alive as long as they exist.

How Closures Work

When a function is created, it captures a reference to the variables it uses from the surrounding scopes. These variables are stored in a special context object that persists as long as the closure exists. This is why closures can have private state.

Closures in Callbacks

Closures are extremely useful in asynchronous programming and event handlers, where you want to remember some state for later.

DARTRead-only
1
void main() {
  var buttons = ['Button1', 'Button2', 'Button3'];

  for (var i = 0; i < buttons.length; i++) {
    // Create a closure that captures i
    var button = buttons[i];
    // Simulate a button click handler
    var handler = () {
      print('$button clicked');
    };
    // Later, when the button is clicked
    handler();
  }
}

If you didn't capture i correctly in a loop, you might get unexpected behavior. Closures capture variables by reference, so you need to be careful with loop variables.

Common Pitfall: Capturing Loop Variables

A classic mistake is creating closures inside a loop that capture the loop variable. By the time the closure is executed, the loop variable may have changed.

DARTRead-only
1
void main() {
  List<Function> functions = [];

  for (var i = 0; i < 3; i++) {
    functions.add(() => print(i)); // captures i by reference
  }

  for (var f in functions) {
    f(); // prints 3, 3, 3 (if i was var), or 0,1,2 if using final?
  }
}
// In Dart, with var, it prints 3,3,3 because there's only one i variable.
// To fix, introduce a new local variable each iteration.

The solution is to create a new variable inside the loop that holds the current value.

DARTRead-only
1
void main() {
  List<Function> functions = [];

  for (var i = 0; i < 3; i++) {
    var current = i; // new variable each iteration
    functions.add(() => print(current));
  }

  for (var f in functions) {
    f(); // prints 0, 1, 2
  }
}

Closures and Asynchronous Code

Closures are essential in async programming, especially with Future.then or Stream.listen. They capture the necessary state to handle the result later.

DARTRead-only
1
Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 1));
  return 'Data';
}

void main() {
  var id = 42;
  fetchData().then((data) {
    // This closure captures id
    print('Data for id $id: $data');
  });
  print('Waiting...');
}

Key Takeaways

    • A closure is a function that captures variables from its lexical scope.
    • Closures enable functions to have private state that persists between calls.
    • Each closure instance has its own copy of captured variables.
    • Be careful when capturing loop variables – create a new local variable to capture the current value.
    • Closures are heavily used in callbacks, event handlers, and functional programming.

Try it yourself

void main() {
  Function makeAdder(int addBy) {
    return (int i) => i + addBy;
  }

  var add5 = makeAdder(5);
  var add10 = makeAdder(10);

  print(add5(3)); // 8
  print(add10(3)); // 13
}

Test Your Knowledge

Q1
of 4

What is a closure?

A
A function that has a name
B
A function that captures variables from its lexical scope
C
A class that contains only static methods
D
A built‑in Dart library
Q2
of 4

What will the following code print? void main() { var funcs = []; for (var i = 0; i < 3; i++) { funcs.add(() => print(i)); } for (var f in funcs) { f(); } }

A
0 1 2
B
3 3 3
C
2 2 2
D
Error
Q3
of 4

How can you fix the loop capture problem?

A
Use `final` instead of `var` for the loop variable
B
Create a new variable inside the loop and capture it
C
Use `async`/`await`
D
Use a `while` loop instead
Q4
of 4

In Dart, do closures capture variables by value or by reference?

A
By value
B
By reference
C
It depends on the type
D
Only primitives are captured by value

Frequently Asked Questions

What is the difference between a closure and a regular function?

All functions in Dart are technically closures because they can access variables from outer scopes. However, the term is often used for functions that outlive the scope where they were created (e.g., returned from another function) and that maintain captured variables.

How does Dart manage memory for closures?

When a closure captures variables, those variables are moved to the heap (if necessary) and remain alive as long as the closure itself is reachable. The garbage collector frees them when the closure is no longer referenced.

Can a closure capture a variable that is later modified?

Yes, closures capture variables by reference, so if the original variable changes after the closure is created, the closure will see the new value. This can lead to surprises, especially with loop variables.

How do I fix the loop capture issue?

Create a new variable inside the loop body that holds the current value, and capture that variable instead. In Dart, using final or var inside the loop creates a fresh variable each iteration.

Are closures only for anonymous functions?

No, named functions can also be closures if they are defined inside another function and reference outer variables. However, anonymous functions are the most common use case.

Previous

dart higher order functions

Next

dart async await

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.