skip to content

In Dart's type system, what do void, Never and Null each mean, and where does each typically appear in code?

level: middleimportance: should knowfreq 33%

answer

  1. void: value not to be used
  2. Never: expression cannot complete
  3. throw has type Never
  4. Null: the type of null
  5. top Object?, bottom Never

basics

~20 s

In Dart, void marks a value that must not be used, typically a return type; Never is the bottom type with no values, for expressions that cannot complete, such as throw; and Null is the type whose only value is null, a subtype of every nullable type.

solid answer

~50 s

`void` says a value is never used. It is the usual return type of functions that do their work through side effects, and using a `void` result is an error ("This expression has a type of 'void' so its value can't be used"); the async counterpart is `Future<void>`. `Never` is Dart's **bottom type**: it has no values, so an expression of type `Never` cannot finish normally. A `throw` expression has static type `Never`, and a helper declared `Never fail(String message)` tells flow analysis that code after the call is unreachable, which also lets it promote variables. `Null` is the class of `null`, whose only value is `null`. Since null safety it is no longer the bottom type and is **not** a subtype of `Object`; it is a subtype of every nullable type such as `String?`. The top type is `Object?`, the bottom type `Never`.

code

dart · 18 lines
dart
Never invalidRow(int line, String reason) =>
    throw FormatException('Row $line: $reason');

void logImport(String name) => print('imported $name');

String readName(Object? raw, int line) {
  if (raw is! String) invalidRow(line, 'name must be text');
  return raw; // promoted to String: invalidRow never returns
}

void main() {
  final name = readName('Soup', 1);
  logImport(name);
  // final result = logImport(name); print(result); // error: void value can't be used
  final Object? raw = 650;
  final int cents = (raw as int?) ?? (throw FormatException('no price')); // throw is Never
  print(cents);
}

go deeper

for a junior

Recall that void marks a result you ignore, Never means an expression cannot complete, and Null is the type of null.

for a middle

Explain top and bottom types under null safety, why throw has type Never, and how Never helpers enable promotion.

for a senior

Use Never-returning validators and Future<void> signatures to make APIs and flow analysis say exactly what they mean.

for a principal

Guide API design so special types clarify intent in shared libraries rather than becoming type-system trivia.

## Four special types in one picture | Type | Values | Role | |---|---|---| | `Object?` | Every value | Top type: supertype of everything | | `void` | Any value, but you may not use it | Marks results to ignore | | `Null` | Only `null` | Type of `null`; subtype of every `T?` | | `Never` | None | Bottom type: subtype of everything | The null-safety guide explains the shift: before null safety, `Object` was the top type and `Null` the bottom type. Now `Object` is non-nullable, so the top type is `Object?`, and a new bottom type, `Never`, replaced `Null` in that role. ## void: "don't use this value" `void` is mainly a **return type** for functions called for their effects: ```dart void logImport(String name) => print('imported $name'); ``` Its rule is about **use**, not about values. The analyzer rejects using the result (`use_of_void_result`), so `var x = logImport('Soup');` followed by using `x` is an error. For asynchronous functions that return nothing, Effective Dart says to use **`Future<void>`**, which older code wrote as `Future` or `Future<Null>` before `void` was allowed as a type argument. ## Never: "this cannot complete" `Never` has no values. An expression of type `Never` must throw, loop forever or otherwise not return: - The static type of a **`throw` expression** is `Never`. - A function declared to return `Never` must never return normally: it throws or never finishes. - Because `Never` is a subtype of every type, a `throw` fits anywhere a value is expected, as in `final price = raw ?? (throw FormatException('missing price'));`. The practical payoff is **reachability analysis**. With ```dart Never invalidRow(int line, String reason) => throw FormatException('Row $line: $reason'); ``` code after `if (raw is! String) invalidRow(3, 'name');` is only reached when `raw` is a `String`, so flow analysis promotes it. The null-safety guide shows the same pattern with a `wrongType` helper. ## Null: the type of null `Null` is a class with a single value, `null`. Under sound null safety: 1. `Null` is **not** a subtype of `Object`; `Object` means non-null. 2. `Null` **is** a subtype of every nullable type: `String?` is effectively `String` or `Null`. 3. You rarely write `Null` directly; `Never` is the right bottom type, and `T?` is the right way to allow `null`. ## Common confusions - "A `void` function returns `null`, so I can check the result." You cannot use the result at all. - "`Never` and `void` both mean no value." `void` produces a value you must ignore; `Never` produces none because evaluation never finishes. - "`Null` is the bottom type." It was, before null safety; now it is `Never`. ## A quick self-test - `void f() {}` then `print(f());`: an analyzer error, because the void result is used. - `Never g() => throw StateError('x');`: valid, since `throw` has type `Never`. - `Object o = null;`: an error, because `Null` is not a subtype of `Object`. - `String? s = null;`: valid, because `Null` is a subtype of `String?`. - `int n = throw ArgumentError();`: valid, because `Never` is a subtype of `int`. If those five answers come quickly, the model is in place: `Object?` on top, `Never` at the bottom, `Null` just below every nullable type, and `void` as a promise not to look. ## Where they show up in a menu importer - `void` on `logImport` and event handlers. - `Future<void>` on `Future<void> importMenu(Uri source)`. - `Never` on a `invalidRow` helper that turns bad rows into `FormatException`s and lets the analyzer promote after checks. - `Null` almost never by name; nullable fields are written `String?`.

  • Why can a throw expression appear after ?? in Dart?
    A `throw` expression has static type `Never`, and `Never` is a subtype of every type, so it fits wherever a value is expected. `raw ?? (throw FormatException('missing'))` therefore has the non-nullable type of `raw`.
  • Why is Null no longer a subtype of Object in null-safe Dart?
    If `Null` stayed a subtype of `Object`, or stayed the bottom type, every type would still accept `null` and null safety would be impossible. So `Object` now means any non-null value, `Object?` became the top type, and the new `Never` took over as the bottom type.

saying these in an interview costs you the question

  • Null is still Dart's bottom type under null safety
  • A void function's result can be checked against null
  • Never and void are two names for the same idea
  • Null is a subtype of Object, so Object accepts null
  • A Never-returning function may return normally on some paths