skip to content

Function Types & Callables

Function types like int Function(String) type callbacks, typedefs name them, and any class with a call() method can be invoked like a function. Interviewers probe typed callbacks and composition.

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

explore

questions

5

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%

answer

  1. mirror of a function header
  2. name swapped for a keyword
  3. positional names optional, named names kept
  4. anywhere a type annotation goes
  5. nullable callback: ?.call()

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.

solid answer

~40 s

Dart builds a function type by taking a function's header and swapping the name for the keyword `Function`, so `int Function(String)` is any function that takes one positional `String` and returns an `int`. Positional parameter names are optional and only document intent, but named parameters must keep their names because callers pass them by name: `void Function(String, {required int retries})`. Optional positionals go in square brackets, just as in a declaration. The type works anywhere a type annotation does: a callback parameter, a field, a local, a return type, or a type argument such as `List<void Function()>`. For an optional callback make the type nullable, `void Function()?`, and invoke it with `onDone?.call()`.

code

dart · 11 lines
dart
void greet(String name, {String greeting = 'Hello'}) =>
    print('$greeting $name!');

void Function(String, {String greeting}) g = greet;

List<int Function(int)> steps = [(x) => x + 1, (x) => x * 2];

void main() {
  g('Dash', greeting: 'Howdy');
  print(steps.fold(3, (acc, step) => step(acc))); // 8
}

go deeper

for a junior

Be able to write int Function(String) from memory, use it on a callback parameter, and say that positional names are optional while named ones are required.

for a middle

Explain every position a function type may occupy, how optional and named parameters are spelled inside it, and why a nullable callback is invoked through ?.call().

for a senior

Show that you design APIs with precise callback signatures, including required named parameters, and recognise the older function-typed parameter style that the recommended lints flag.

for a principal

Treat callback signatures as public contracts: adding a required parameter to a function type breaks every caller that passes a function, so shape them deliberately and early.

## What a function type is In Dart, functions are **first-class objects**: a function can be stored in a variable, passed as an argument and returned from another function. To do that safely the type system needs a way to describe *which* functions fit a slot. That description is a **function type**. The rule is mechanical: take a function declaration's header and replace the function's **name** with the keyword `Function`. ```dart int parseScore(String raw) => int.parse(raw); int Function(String) parser = parseScore; // the type of parseScore void main() { print(parser('42')); // 42 } ``` Read `int Function(String)` as: **return type** `int`, then the keyword, then the **parameter list** `(String)`. Nothing else is needed; no interface, no declaration, no typedef. Any function whose shape matches can be assigned. ## Writing the parameter list The parameter list inside a function type follows the same grammar as a declaration, with one relaxation and one restriction: - **Positional parameter names are optional.** `int Function(String)` and `int Function(String raw)` are the same type; the name is documentation only. - **Named parameter names are mandatory.** Callers pass named arguments by name, so the name is part of the contract: `void Function({required int retries})`. - **Optional positional parameters** go in square brackets: `String Function(String, [int])`. - **Required named parameters** carry `required`, exactly as in a declaration. - **Generic function types** put type parameters after the keyword: `T Function<T>(T)` is the type of a function that is itself generic. - A function with *extra optional* parameters still fits a shorter type, because it can be called with fewer arguments. Because positional names are ignored, `bool Function(String a)` and `bool Function(String b)` are the same type, and the core `print` function, declared as `void print(Object? object)`, fits `void Function(Object?)` whatever its parameter is called. Named parameters behave differently: `void Function({int? limit})` and `void Function({int? max})` are distinct types, and a function declared with `{int? limit}` fits only the first. Renaming a named parameter in a public callback type is therefore a breaking change for every function that callers pass in. ## Where a function type can appear A function type is an ordinary type, so it is legal anywhere a type annotation is: 1. A **parameter**: `void retry(Future<void> Function() action)`. 2. A **field**: `final bool Function(String) isAllowed;`. 3. A **local variable**: `int Function(String) parser = parseScore;`. 4. A **return type**: `bool Function(String) longerThan(int n) => (s) => s.length > n;`. 5. A **type argument**: `List<void Function()> listeners = [];`. 6. A **record field**: `({String label, void Function()? onPressed})`. ## Nullable callbacks and `call` Optional callbacks are very common, so the type is often nullable: `void Function()? onDone`. Every function type has a `call` method with the same signature as the type itself, and calling `call` behaves exactly like calling the function. That is what makes the idiom work: ```dart class Uploader { Uploader({this.onDone}); final void Function()? onDone; void finish() => onDone?.call(); // skipped when onDone is null } ``` Writing `onDone()` directly does not compile while the type is nullable. An `if (onDone != null) onDone();` check does not help either, because Dart promotes only private final fields (since 3.2), never a public one like this; `?.call()` is the concise, sound form. ## Three ways to type a callback parameter | Style | Example | Status | |---|---|---| | **Function type syntax** | `bool Function(String) test` | Recommended everywhere | | **Function-typed formal parameter** | `bool test(String s)` | Still valid; the `use_function_type_syntax_for_parameters` lint (recommended set) flags it | | **Bare `Function`** | `Function test` | Compiles, but carries no signature and makes every call dynamic | The older C-like style is not wrong, but the function type syntax is the one that works in every position, so Effective Dart asks you to use it for parameters too. ## What interviewers listen for - Writing the type without hesitation, with the return type *before* `Function`. - Knowing that named parameters keep their names and positional ones need not. - Choosing `void Function()?` plus `?.call()` for optional callbacks. - Not reaching for a typedef by reflex: inline function types are the default recommendation, and a typedef is only an alias for the same type. - Not confusing Dart's syntax with arrow-style type notation from other languages; `(String) => int` is not a type annotation in Dart.

  • Why must named parameters keep their names in a Dart function type when positional ones need not?
    Callers pass named arguments by name, so the name is part of the function's contract: `void Function({required int retries})` and `void Function({required int attempts})` are different types. Positional arguments are matched by position, so a positional name inside a function type is documentation only and changes nothing about which functions fit.
  • Can a function with an extra optional parameter be stored in a `void Function(String)` variable?
    Yes. `void log(String msg, [int level = 0])` can be called with a single `String`, so it satisfies `void Function(String)`; the extra parameter keeps its default. The reverse fails: a function that requires two arguments cannot sit where callers will pass only one.

saying these in an interview costs you the question

  • Writing (String) => int as a Dart type annotation
  • Believing named parameters can be left unnamed in a function type
  • Thinking a typedef is required before a function type can be used
  • Calling a nullable callback directly and expecting a runtime null check
  • Putting the return type after the parameter list
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, 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