Skip to content
TypeScript ts basic-types 4 min read

The void Type

The void type represents the absence of a meaningful return value. It is the inferred return type of any function that runs for its side effects and never reaches a return statement with a value — console.log, an event handler, a forEach callback. void is more than “returns nothing,” though: it has a deliberate, slightly surprising substitutability rule that makes callback APIs ergonomic. This page covers void as a return type, void as a value type, the substitution quirk, and how void differs from undefined and never.

void as a return type

The most common use of void is annotating a function that returns nothing. You rarely write it by hand because TypeScript infers it, but it is useful on declarations and types.

function logMessage(message: string): void {
  console.log(message);
  // no return statement, or a bare `return;`
}

const noop: () => void = () => {};

A void-returning function may contain a bare return; (early exit) but not return someValue;. Annotating void documents intent and prevents you from accidentally relying on a return value that does not exist.

A void function may still return a value

Here is the key nuance: a function typed as returning void is allowed to be implemented by a function that actually returns something. The contract () => void only promises that callers must ignore whatever comes back — it does not forbid a value from existing.

type Callback = () => void;

// ✅ Allowed: the returned number is simply ignored by callers
const cb: Callback = () => 42;

const result = cb(); // type of `result` is `void`, value at runtime is 42

This rule, called contextual void substitution, exists so that functions returning real values can be passed where a void callback is expected. The compiler narrows the apparent return type to void, so callers cannot use the leaked value, but no error is raised at the call site.

Why this matters for callbacks

The substitution rule is what makes array methods like forEach accept arbitrary callbacks without complaint. Without it, passing a function that happens to return a value would be an error.

const numbers = [1, 2, 3];
const dest: number[] = [];

// `push` returns a number (the new length), but forEach expects a void callback.
// Substitution lets this compile cleanly:
numbers.forEach((n) => dest.push(n));

Array.prototype.forEach expects (value: T, index: number, array: T[]) => void. push returns the array’s new length, yet the callback is accepted because void ignores the return. Relying on this keeps everyday code free of needless void casts or wrapper braces.

void as a value type

When void appears as a variable type rather than a return type, its set of legal values is tiny. Under strictNullChecks, the only assignable value is undefined; without strict mode, null is also allowed.

let unusable: void;
unusable = undefined; // ✅ ok
// ❌ Error: Type 'null' is not assignable to type 'void' (under strictNullChecks)
unusable = null;
// ❌ Error: Type 'number' is not assignable to type 'void'
unusable = 1;

A void variable is almost never useful — there is nothing meaningful you can do with it. Treat void as a return-position type and reach for undefined if you genuinely need a value that is “nothing.”

void vs undefined vs never

These three types all describe “not a normal value,” but they mean different things. void says the return is meaningless and should be ignored; undefined is a concrete value; never says the function never returns at all (it throws or loops forever).

function returnsVoid(): void {}          // returns, value ignored
function returnsUndefined(): undefined { // must explicitly return undefined
  return undefined;
}
function throws(): never {                // never finishes normally
  throw new Error("boom");
}

The contrast is sharpest in return annotations. A void function can return; implicitly; an undefined function must produce the literal undefined; a never function must be unable to reach its end.

TypeMeaningAllowed valuesCallback substitution
voidReturn value is meaninglessundefined (and null without strict)Yes — accepts functions returning anything
undefinedThe concrete value undefinedonly undefinedNo
neverFunction never returns(no values)N/A

Do not annotate a callback parameter as () => undefined when you mean “I don’t care about the return.” Use () => void so callers can pass value-returning functions without errors.

Best Practices

  • Let TypeScript infer void for side-effecting functions; annotate it explicitly on public APIs and callback type aliases for clarity.
  • Use () => void (not () => undefined) for callback parameters to benefit from substitutability.
  • Do not depend on a value returned from a void-typed function — the compiler treats it as void.
  • Avoid void as a variable type; prefer undefined if you truly need an empty value.
  • Reach for never, not void, when a function always throws or never terminates.
  • Remember the substitution rule applies to assignment of function types, not to direct calls where you read the result.
Last updated June 29, 2026
Was this helpful?