Type expression

void and never

  • Basics
  • Tested with TypeScript 5.9

void is the return type of a function whose result does not matter: @returns {void}. In a callback type, (id: string) => void means the caller will ignore whatever the callback returns, so a callback that happens to return something is still accepted.

never is the type of something that cannot happen: a function that always throws, or a value after every possible case has been handled. TypeScript uses it to prove that code is exhaustive.

At a glance

Use JSDoc in a .js file TypeScript in a .ts file
Returns nothing useful /** @returns {void} */ (): void
A callback whose result is ignored /** @param {(id: string) => void} onSaved */ (onSaved: (id: string) => void)
Never returns /** @returns {never} */ (): never

Examples

save.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/**
 * @param {string} draft
 * @param {(id: string) => void} onSaved
 */
export function save(draft, onSaved) {
  const id = `draft-${draft.length}`;
  onSaved(id);
}

/** @type {string[]} */
const saved = [];
// push returns a number; the void callback type accepts it and ignores the result.
save("Spring sale", (id) => saved.push(id));
A callback typed to return void can return anything.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
order.js order.ts Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: order.js(22,24): error TS2345: Argument of type '"refunded"' is not assignable to parameter of type 'never'. order.ts(17,24): error TS2345: Argument of type '"refunded"' is not assignable to parameter of type 'never'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. order.js(22,24): error TS2345: Argument of type '"refunded"' is not assignable to parameter of type 'never'. order.ts(17,24): error TS2345: Argument of type '"refunded"' is not assignable to parameter of type 'never'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. order.js(22,24): error TS2345: Argument of type '"refunded"' is not assignable to parameter of type 'never'. order.ts(17,24): error TS2345: Argument of type '"refunded"' is not assignable to parameter of type 'never'. checked 21 October 2026
/** @typedef {"pending" | "shipped" | "delivered" | "refunded"} OrderStatus */

/**
 * @param {never} value
 * @returns {never}
 */
function unhandled(value) {
  throw new Error(`Unhandled status: ${value}`);
}

/** @param {OrderStatus} status */
export function label(status) {
  switch (status) {
    case "pending":
      return "Waiting to ship";
    case "shipped":
      return "On the way";
    case "delivered":
      return "Delivered";
    default:
      return unhandled(status);error TS2345: Argument of type '"refunded"' is not assignable to parameter of type 'never'.
  }
}
type OrderStatus = "pending" | "shipped" | "delivered" | "refunded";

function unhandled(value: never): never {
  throw new Error(`Unhandled status: ${value}`);
}

export function label(status: OrderStatus) {
  switch (status) {
    case "pending":
      return "Waiting to ship";
    case "shipped":
      return "On the way";
    case "delivered":
      return "Delivered";
    default:
      return unhandled(status);error TS2345: Argument of type '"refunded"' is not assignable to parameter of type 'never'.
  }
}
never makes a switch prove it handled every case.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
stock.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/**
 * @param {string} message
 * @returns {never}
 */
function fail(message) {
  throw new Error(message);
}

/** @param {number | undefined} stock */
export function reserve(stock) {
  if (stock === undefined) fail("Unknown product");
  return stock - 1;
}
A function that never returns narrows what follows.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Exhaustive checks with never

After a switch has handled every member of a union, the value left in default has type never. Passing it to a function that takes never type-checks only while that is true. Add a member to the union without a matching case, and the call fails (TS2345), pointing you at the switch that needs updating, as the second example shows.

Functions that never return

@returns {never} on a function that always throws tells TypeScript that code after a call to it is unreachable. In the third example, stock is number after if (stock === undefined) fail(…), because the fail call ends that path.

For that narrowing to work, the function needs the explicit @returns {never} and must be a declaration TypeScript can see, as here.

Gotchas

  • A function declared @returns {void} that returns a value is an error; a callback typed => void that returns a value is not. The difference is deliberate, so array.forEach((x) => list.push(x)) works.
  • undefined and void are close but not the same: before TypeScript 5.1, a function typed to return undefined needed an explicit return.

Questions

What does never mean in TypeScript?

A type with no values. A function returning never does not return at all, and a variable of type never can only exist in code TypeScript has proven unreachable.

Sources

Last reviewed 20 October 2026.