In Dart, when is a function typedef worth declaring, and why is the old form `typedef bool TestNumber(num);` discouraged?
answer
- an alias, not a new type
- inline types are the default
- long or repeated signatures earn a name
- legacy form: lone identifier is a name
- generic alias vs generic function type
basics
~20 sA 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.
solid answer
~40 sA typedef names a type; it does not create one. After `typedef Compare<T> = int Function(T a, T b);`, any function with that shape `is Compare<int>` without declaring anything, so the alias exists purely for readability. Effective Dart prefers inline function types at the point of use and keeps typedefs for signatures that are long or repeated across an API. Always use the `=` form: in the legacy `typedef bool TestNumber(num);` a single identifier is parsed as the parameter's *name*, so the parameter's type is actually `dynamic`, and the old form cannot name a generic function type at all. The `prefer_generic_function_type_aliases` lint, in the core set, flags it. Since Dart 2.13 a typedef can alias any type, not only function types.
code
dart · 12 lines// Legacy: num is the parameter NAME, its type is dynamic.
typedef bool LegacyTestNumber(num);
// Current: num is the parameter TYPE.
typedef TestNumber = bool Function(num);
bool isPositive(num n) => n > 0;
void main() {
TestNumber t = isPositive;
print(t(3)); // true
}go deeper
Recall that a typedef just gives a function type a name, and that the modern form is typedef Name = ReturnType Function(params).
Explain that aliases are structural, so any matching function already is one, and why the legacy syntax silently makes a lone identifier a dynamic parameter.
Decide inline versus named signatures deliberately, enforce prefer_generic_function_type_aliases, and distinguish a generic alias from an alias of a generic function type.
Treat aliases as vocabulary for an API surface: a few meaningful names help readers, while an alias per callback hides the real shape and implies safety it does not give.
## A typedef is an alias The keyword `typedef` declares a **type alias**: a new *name* for a type that already exists. It does not declare a new type. Function types in Dart are **structural**, meaning a function fits a function type because of its shape, never because it opted in. So: ```dart typedef Compare<T> = int Function(T a, T b); int byLength(String a, String b) => a.length - b.length; void main() { print(byLength is Compare<String>); // true: same type, two names } ``` `byLength` never mentions `Compare`, and still *is* one. The practical consequence: a typedef gives you readability and a single place to change a signature, but no extra type safety. Two aliases with the same underlying function type are interchangeable. ## When to name a function type Effective Dart's default is **PREFER inline function types over typedefs**: readers want to see `bool Function(Event)` right where it is used. A name starts to pay off when: - the signature is **long**, for example several named parameters or a function that returns another function; - the same signature appears across **many members** of an API, so one edit updates them all; - the name carries **domain meaning** that the raw shape does not, such as `Validator` or `Compare<T>`; - the type is **nested**, as in `List<Future<void> Function(Event)>`, where an alias keeps declarations readable. A typical case is a function that returns a function. Written inline, a wrapper that takes a handler and returns a new handler reads `Future<String> Function(String) Function(Future<String> Function(String))`. With `typedef Handler = Future<String> Function(String request);` the same declaration becomes `Handler Function(Handler inner)`, which a reviewer can check at a glance. The alias changes nothing about which functions fit; it only changes how many brackets a reader has to match. For a one-off private callback, the inline type is clearer. The old `avoid_private_typedef_functions` lint that pushed this was **deprecated as of Dart 3.13**, because a private alias can also aid readability. ## The two syntaxes | | Legacy form | Current form | |---|---|---| | Declaration | `typedef bool TestNumber(num);` | `typedef TestNumber = bool Function(num);` | | What `(num)` means | a parameter **named** `num`, typed `dynamic` | a parameter **typed** `num` | | Can name a generic function type | No | Yes | | Can alias non-function types | No | Yes, since Dart 2.13 | | Status | Deprecated, still compiles | Recommended | The legacy problem is subtle: in `typedef bool TestNumber(num);` a lone identifier in a parameter position is read as the parameter's **name**. The typedef therefore describes a function that takes *anything* and returns `bool`. This was a long-standing source of bugs. The `prefer_generic_function_type_aliases` lint, enabled in the core, recommended and flutter sets, reports every legacy typedef. ## Generic alias versus generic function type The current syntax can express two different things that look alike: 1. **A generic alias**: `typedef Transform<T> = T Function(T);`. Each use fixes `T`, so `Transform<int>` is the ordinary type `int Function(int)`. A bare `Transform` means `Transform<dynamic>`. 2. **An alias for a generic function type**: `typedef Identity = T Function<T>(T);`. A value of this type is itself generic and can be called with any type argument. ```dart typedef Identity = T Function<T>(T value); T id<T>(T value) => value; void main() { Identity f = id; // only a generic function fits print(f<int>(3)); // 3 print(f('text')); // text, T inferred as String int Function(int) g = id; // implicit generic instantiation print(g(4)); // 4 } ``` The legacy syntax could not express the second form: referencing a legacy generic typedef without type arguments silently gave `int Function(dynamic, dynamic)` rather than a generic function type. ## Beyond functions Since Dart 2.13 a typedef can alias any type, such as `typedef IntList = List<int>;` or `typedef ListMapper<X> = Map<X, List<X>>;`. The same rule holds: the alias and its target are the same type. If you need a distinct compile-time type over an existing representation, an alias is the wrong tool. ## Review checklist - Inline by default; name a signature once it is long, repeated or meaningful. - Only the `=` syntax, never `typedef R Name(params);`. - Do not expect an alias to stop callers mixing up two same-shaped functions.
- What is the difference between `typedef Transform<T> = T Function(T);` and `typedef Identity = T Function<T>(T);`?The first is a generic alias: each use picks one type, so `Transform<int>` is simply `int Function(int)`, and bare `Transform` means `Transform<dynamic>`. The second names a generic function type: a value of it is itself generic and can be called with any type argument, like `T id<T>(T x) => x`. Only functions that are themselves generic fit the second.
- Does `typedef UserId = String;` make UserId a distinct type from String?No. Since Dart 2.13 a typedef may alias any type, but the alias and its target are the same type, so passing any `String` where a `UserId` is expected compiles. For a distinct compile-time type over the same representation, Dart 3.3 added extension types; a typedef only renames.
saying these in an interview costs you the question
- A typedef creates a nominal type other functions cannot match
- typedef bool TestNumber(num) declares a function taking a num
- Every callback type in an API should get its own typedef
- Typedefs can only alias function types
- A generic typedef and a generic function type mean the same thing