In Dart's type system, what do void, Never and Null each mean, and where does each typically appear in code?
answer
- void: value not to be used
- Never: expression cannot complete
- throw has type Never
- Null: the type of null
- top Object?, bottom Never
basics
~20 sIn 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 linesNever 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
Recall that void marks a result you ignore, Never means an expression cannot complete, and Null is the type of null.
Explain top and bottom types under null safety, why throw has type Never, and how Never helpers enable promotion.
Use Never-returning validators and Future<void> signatures to make APIs and flow analysis say exactly what they mean.
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