why is return type `null` (or any other type) assignable to return type `void`?

Canonical answers to this question can be found in:


It is the intended behavior, not a bug. TypeScript’s void type is usually supposed to represent something unusable, not necessarily absent.

On the one hand, the compiler assumes that it is probably an error for you to intentionally and explicitly assign a value of any non-undefined type to a variable or property of type void.

On the other hand, it treats a function whose return type of void to mean “callers cannot safely use the return value of this function”, not “this function will definitely return undefined.” Therefore it lets you assign a non-void-returning function value anywhere a void-returning function is expected, since the caller would never be checking the return value anyway.

These two situations are, on the face of it, inconsistent; generally speaking, due to covariance of return types, the type ()=>A is assignable to ()=>B if and only if A is assignable to B. This breaks down with void, and is presumably why you are bothered by the situation.


But despite being inconsistent it is, for better or worse, intentional. The reason is that it is incredibly useful to be able to ignore callback return values, especially for arrow functions which happen to have side effects and return values. The go-to example here is Array.prototype.push(), which mutates the array you call it on and returns its new length. I want to call

const arr1 = [4, 5, 6];
const arr2 = [1, 2, 3];
arr1.forEach(v => arr2.push(v))

without having forEach() get angry at me because push() returns a number instead of the void it was promised:

interface Array<T> {
    pedanticForEach(cb: (val: T) => undefined): void;
}
Array.prototype.pedanticForEach = Array.prototype.forEach;

arr1.pedanticForEach(v => arr2.push(v)); // error!
arr1.pedanticForEach(v => (arr2.push(v), undefined)); // okay
arr1.pedanticForEach(v => void arr2.push(v)); // okay

But the analogous operation with void values themselves is much less useful. There is no common use case in which someone really wants to assign a number value explicitly to a variable of type void. It’s probably an error when you do it.

In order to make it consistent, they’d either have to allow this probable error, or force people to wrap their not-actually-void returning callback functions with something explicitly void-ish. Either of which would hurt productivity.

So usefulness and developer productivity win over soundness and consistency in this case. Such trade-offs are common in the language; strict soundness and type safety is not one of TypeScript’s design goals. In fact, TypeScript Design Non-Goal #3 is to

Apply a sound or “provably correct” type system. Instead, strike a balance between correctness and productivity.


Playground link to code

Leave a Comment