skip to content

In Dart, what is `FutureOr<T>`, and why does Effective Dart accept it in parameters and callbacks but not as a return type?

level: middleimportance: nice to knowfreq 22%

answer

  1. a union of T and Future<T>
  2. cannot be instantiated
  3. generous in what it accepts
  4. returning it forces callers to check
  5. test is Future<T> first

basics

~20 s

FutureOr<T> is Dart's union of T and Future<T>: a value that is either a plain T or a future of one. Accept it in parameters and callback return types, but return Future<T>, so callers never check which they got.

solid answer

~40 s

`FutureOr<T>` from `dart:async` is a special union type meaning *either a `T` or a `Future<T>`*; it cannot be instantiated or extended. The SDK uses it where either shape is fine to receive: `then`'s callback returns `FutureOr<R>`, and `timeout`'s `onTimeout` returns `FutureOr<T>`, so you can return a plain value or a future. Effective Dart says to **avoid it as a return type**: a caller of a `FutureOr<int>` function must check what it got, or await it anyway, so return `Future<int>` and keep the function consistently asynchronous. The precise rule is to use it only in contravariant positions, meaning parameters and a callback's return type. When you inspect one, test `is Future<T>` first, because if `T` could be `Object`, a future also passes `is T`.

go deeper

for a junior

Know that FutureOr<T> means a value that is either a T or a Future of T, and that you can await it.

for a middle

Explain where the SDK uses FutureOr, such as then and onTimeout callbacks, and why functions should return Future<T> instead.

for a senior

Apply the contravariant-position rule in API design and branch on FutureOr values safely by testing is Future<T> first in generic code.

for a principal

Keep public APIs consistently synchronous or asynchronous, accepting FutureOr where it helps callers but never exposing mixed timing in results.

## What the type means `FutureOr<T>`, declared in `dart:async`, is a **union type**: a value of type `FutureOr<int>` is either an `int` or a `Future<int>`. Dart has no general union types, so `FutureOr` is special-cased by the type system. It is declared as a class only so it has a name; the SDK's constructor throws `UnsupportedError`, and it cannot be instantiated, extended or implemented in practice. Some consequences documented in the SDK: - `FutureOr<Object>` is equivalent to `Object`, since every future is already an `Object`. - `FutureOr<FutureOr<Object>>` and `FutureOr<Future<Object>>` collapse similarly. - `await` accepts a `FutureOr<T>` and yields a `T` either way. ## Where the SDK uses it `FutureOr` appears wherever an API wants to be **generous in what it accepts**: - `Future<R> then<R>(FutureOr<R> onValue(T value), ...)`: your callback may return a value or a future. - `Future<T> timeout(Duration timeLimit, {FutureOr<T> onTimeout()?})`: the fallback may be a cached value or another load. - The typed `onError<E>` extension's handler returns `FutureOr<T>`. In every case `FutureOr` sits in a **callback's return type**, which is an input from the API's point of view. ## Why not as a return type Effective Dart's rule is **AVOID using `FutureOr<T>` as a return type**. Compare: ```dart // Good: accepts either, always returns a future Future<int> triple(FutureOr<int> value) async => (await value) * 3; // Bad: callers must check which one they got FutureOr<int> triple2(FutureOr<int> value) { if (value is int) return value * 3; return value.then((v) => v * 3); } ``` A caller of `triple2` has to inspect the result before using it, or simply `await` it, which treats it as a future anyway. A function that is **sometimes synchronous and sometimes asynchronous** is hard to use correctly, because code after the call may or may not run before the work is done. The guide states the precise version: use `FutureOr<T>` only in **contravariant positions**. Parameters are contravariant; return types are covariant; and in a parameter whose type is a function, the callback's return type flips back to contravariant. That is why `FutureOr<S> Function(T) callback` as a parameter is fine. | Position | Example | OK? | |---|---|---| | Parameter | `Future<int> triple(FutureOr<int> value)` | yes | | Callback return inside a parameter | `FutureOr<S> Function(T) callback` | yes | | Function return type | `FutureOr<int> triple2(...)` | avoid | | Field consumers read | `FutureOr<Config> config;` | avoid, same reason | ## Inspecting a `FutureOr` value If you must branch rather than just `await`, Effective Dart says to **test for `Future<T>` first** when `T` could be `Object` or an unconstrained type parameter: ```dart Future<T> logValue<T>(FutureOr<T> value) async { if (value is Future<T>) { final result = await value; print(result); return result; } else { print(value); return value; } } ``` Testing `value is T` first is wrong in generic code: if `T` is `Object`, a `Future<Object>` also satisfies `is T`, so the future would be treated as a finished value. For a concrete type such as `FutureOr<int>`, either test works because `int` and `Future<int>` are disjoint. ## `FutureOr` in your own APIs The same pattern is useful in application code whenever a caller may already have a value. A settings service might take a loader: ```dart Future<Config> loadConfig(FutureOr<Config> Function() loader) async { final config = await loader(); // works for sync and async loaders validate(config); return config; } ``` A test can pass `() => Config.defaults()` without wrapping it in a future, and production code can pass a function that reads a file. The function still returns `Future<Config>`, so every caller sees one consistent, asynchronous contract. ## Summary for an interview 1. It is a union of `T` and `Future<T>`, special in the type system. 2. Accept it; do not return it. 3. Await it when you can, and test `is Future<T>` first when you must branch.

  • Why can `then`'s callback return `FutureOr<R>` without breaking the rule against returning FutureOr?
    Because the callback is a parameter of `then`. From `then`'s point of view, the callback's result is an input it consumes, which is a contravariant position; `then` itself returns a plain `Future<R>`. Effective Dart spells out that a callback's return type in a parameter is an acceptable place for `FutureOr`.

saying these in an interview costs you the question

  • FutureOr<T> is a class you can construct or subclass
  • Returning FutureOr<T> is fine because callers can always await it
  • Testing value is T first is always a safe way to branch on FutureOr
  • FutureOr<Object> is a narrower type than Object
  • await cannot be applied to a FutureOr value