skip to content

Functional Style

In Dart functions are objects: closures capture their scope, tear-offs turn methods and constructors into values, and function types describe them. Interviewers check fluency with callbacks.

part ofDartoverview, primer and where to startread it →
on this pageshow

explore

questions

9

In Flutter, why is onPressed: _save() wrong while onPressed: _save and onPressed: () => _save() both work?

level: juniorimportance: must knowfreq 65%

answer

  1. calling versus referring
  2. parentheses run it now
  3. onPressed expects VoidCallback?
  4. tear-off: the name without ()
  5. lambda: () => or () { }

basics

~20 s

onPressed expects a function to call later; _save() calls the method during build and passes its result, a void value the analyzer rejects. _save passes the method itself as a tear-off, and () => _save() passes an anonymous function that calls it.

solid answer

~40 s

`onPressed` is typed `VoidCallback?`, meaning a `void Function()` or `null`. Writing `_save()` **invokes** the method while `build` runs and passes its return value; for a `void` method the analyzer rejects that argument, and for a method returning a value it is a type mismatch. Writing `_save` without parentheses creates a **tear-off**: a function object that calls `_save` when the button is pressed. Writing `() => _save()` creates an **anonymous function** (a closure) that does the same. Use the tear-off when the signatures match exactly; use a lambda when you need to pass arguments, as in `() => _delete(item)`, run several statements with `() { ... }`, or call `setState`. Passing `null` disables a Material button.

code

dart · 30 lines
dart
import 'package:flutter/material.dart';

class SaveBar extends StatefulWidget {
  const SaveBar({super.key});

  @override
  State<SaveBar> createState() => _SaveBarState();
}

class _SaveBarState extends State<SaveBar> {
  int _saves = 0;

  void _save() => setState(() => _saves++);
  void _saveAs(String name) => setState(() => _saves++);

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        ElevatedButton(onPressed: _save, child: const Text('Save')), // tear-off
        TextButton(
          onPressed: () => _saveAs('copy'), // lambda supplies an argument
          child: const Text('Save copy'),
        ),
        Text('$_saves'),
        // ElevatedButton(onPressed: _save(), ...) would not compile.
      ],
    );
  }
}

go deeper

for a junior

Recall the three spellings and which one runs immediately; write arrow and block lambdas correctly for button callbacks.

for a middle

Explain VoidCallback's type, why the analyzer rejects a void or Future result, and when a tear-off fits versus when arguments force a lambda.

for a senior

Catch the set-literal arrow slip and callbacks whose signature only accidentally matches, and keep callback style consistent in review.

for a principal

Set conventions for callback style and lints, such as preferring tear-offs, so large widget trees stay readable and uniform.

## What `onPressed` wants Flutter's buttons, such as `ElevatedButton`, `TextButton` and `IconButton`, declare `onPressed` as `VoidCallback?`. `VoidCallback` is a typedef for `void Function()`: a function that takes no arguments and returns nothing. The button stores that function and calls it **later**, when the user taps. Passing `null` instead disables the button. So the question is always: are you handing the button **a function**, or **the result of calling one**? ## Three spellings, three meanings | Code | What happens during `build` | What happens on tap | |---|---|---| | `onPressed: _save()` | `_save` runs immediately; its result is passed | nothing useful | | `onPressed: _save` | a tear-off of `_save` is passed | `_save` runs | | `onPressed: () => _save()` | an anonymous function is created and passed | it calls `_save` | The first form is the bug. If `_save` returns `void`, the analyzer refuses to use a `void` result as an argument. If `_save` returns `Future<void>` because it is `async`, the error becomes a type mismatch: a `Future<void>` is not a `void Function()`. Either way the code does not compile, which is the good outcome; the dangerous version is a method whose result happens to be a function, where the code compiles and runs the wrong thing. ## Anonymous functions in Dart An **anonymous function** (also called a lambda or closure) has a parameter list and a body but no name: - **block body**: `() { _validate(); _save(); }` runs any number of statements; - **arrow body**: `() => _save()` is shorthand for a body with a single `return` of one expression. The arrow form allows only **one expression**. `() => { _save(); }` is a classic slip carried over from other languages: in Dart, braces after `=>` start a set or map **literal**, not a block, and a statement ending in `;` cannot appear inside a literal, so the code does not compile. Use `() { ... }` for statements, or `() => _save()` for a single call. Anonymous functions are **closures**: they can read and assign variables from the scope where they were written, such as fields of the `State` object or local variables in `build`. That is what lets `() => _delete(item)` remember which `item` it belongs to. ## Tear-offs Referring to a function, method or constructor **without parentheses** creates a **tear-off**: a function object with the same parameters that calls the original. Examples: - `onPressed: _save` tears off an instance method of the `State`; - `items.forEach(print)` tears off a top-level function; - `names.map(User.new)` tears off a constructor (Dart 2.15 and later). A tear-off fits only where its signature matches the expected function type. `_save` fits `VoidCallback` only if it takes no required arguments. ## Async methods as callbacks An `async` method can be passed the same way. If `_save` is declared `Future<void> _save() async { ... }`, then `onPressed: _save` compiles: a function returning `Future<void>` is assignable to `void Function()`, because a `void` return type accepts any value. Two consequences follow: - the button **ignores** the returned future, so it does not wait for the save and does not see its errors; - handle failures **inside** the method (a `try`/`catch` that shows a message), because nothing outside will. The same applies to `() => _save()`; the lambda also discards the future. ## Choosing between tear-off and lambda 1. **Signatures match exactly**: prefer the tear-off; it is shorter and it is the style Effective Dart recommends. 2. **You need to supply arguments**: use a lambda, `() => _delete(item)`. 3. **You need several statements or `setState`**: use a block lambda, `() { setState(() => _count++); }`. 4. **You want the button disabled sometimes**: pass `null` conditionally, `onPressed: _canSave ? _save : null`. ## Common mistakes - `onPressed: _save()` runs the method during `build`. - `onPressed: () => { _save(); }` does not compile, because braces after `=>` start a collection literal, not a block. - Tearing off a method whose parameters do not match `VoidCallback`, then wrapping it in a cast instead of a lambda.

  • In Dart, why does onPressed: () => { _save(); } not compile?
    After `=>` Dart expects one expression, and an opening brace in expression position starts a set or map literal, not a block of statements. The `;` inside is not valid in a literal, so parsing fails. Write `() { _save(); }` for a block body, or `() => _save()` for a single call.
  • How do you disable a Flutter button based on state while still using a tear-off?
    Pass `null` conditionally: `onPressed: _canSave ? _save : null`. A null `onPressed` renders the button disabled and ignores taps; a non-null callback enables it.

saying these in an interview costs you the question

  • onPressed: _save() registers _save to run on tap
  • A tear-off like _save calls the method once when the widget is built
  • () => { _save(); } is a block body that runs _save
  • An arrow function can contain several statements separated by semicolons
  • A lambda is required whenever a callback touches State fields
open as a page

In Dart, how do you write a function type such as `int Function(String)`, and where can you use one?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A Dart function type is a function header with its name replaced by the keyword Function: int Function(String) accepts one String and returns an int. It can annotate parameters, fields, variables, return types and type arguments.

open as a page

In Dart, what are function, method and constructor tear-offs such as forEach(print) and map(User.new), and when should a lambda stay instead?

level: middleimportance: should knowfreq 45%

basics

~20 s

A tear-off names a function, method or constructor without parentheses, producing a function object that calls it: print, int.parse, buffer.write, User.new. Prefer it over a lambda that only forwards arguments; keep the lambda when arguments must be adapted or the receiver re-read.

open as a page

In Dart, how would you type and compose a validator pipeline where each validator is a `String? Function(String)`?

level: middleimportance: should knowfreq 36%

basics

~10 s

Name the shape once with typedef Validator = String? Function(String value);, write factory functions that return configured validators, and a combinator that takes a List<Validator> and returns one Validator reporting the first non-null error.

open as a page

In Dart, why is typing a callback parameter as plain `Function` weaker than a precise type such as `bool Function(String)`?

level: middleimportance: should knowfreq 44%

basics

~20 s

Plain Function is the supertype of every function type and carries no signature, so calls through it are dynamic: arguments go unchecked until run time and the result is dynamic. A precise type like bool Function(String) is checked at compile time.

open as a page

In Dart, when is a function typedef worth declaring, and why is the old form `typedef bool TestNumber(num);` discouraged?

level: middleimportance: should knowfreq 40%

basics

~20 s

A Dart typedef is only an alias: typedef Compare<T> = int Function(T a, T b); names a type matching functions already have. Declare one for long or repeated signatures. The legacy form reads a lone identifier as a parameter name, leaving it dynamic.

open as a page

In Dart, when a Flutter build method creates button callbacks in a for loop, which index does each callback see, and when do they all share one variable?

level: seniorimportance: should knowfreq 42%

basics

~20 s

A variable declared in a Dart for loop's header is fresh each iteration, so each callback keeps its own index. Callbacks share one variable only when it is declared outside the loop, as with a while-loop counter, and then all see its final value.

open as a page

In Flutter, why does controller.removeListener(_onScroll) work with a method tear-off but silently fail when the listener was added as () => _onScroll()?

level: seniorimportance: should knowfreq 32%

basics

~20 s

ChangeNotifier.removeListener removes the first registered listener that is == to its argument. Two tear-offs of the same method on the same object are equal, so removal works; two separately written lambdas are distinct objects, so nothing is removed and the listener stays attached.

open as a page

In Dart, what does declaring a `call()` method on a class give you, and what does a function-typed parameter actually receive when you pass such an object?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A call() method lets an instance be invoked like a function, obj(x), while keeping fields and other methods. Passed where a function type is expected, Dart implicitly tears off obj.call, so the callee receives a bound method, not the object.

open as a page