Chapter 2.6☕ 15 min read

void & never

One returns nothing, the other never returns at all.

01Chai Wala vs Traffic Jam

Most functions return something — a string, a number, a boolean, an object. But some functions return NOTHING, and some functions return NEVER. These are two fundamentally different concepts that beginners often confuse, but they're as different as chai and traffic jams.

void means "this function doesn't return a value — it does its job and finishes." The function completes normally, control returns to the caller, but there's no value to use. It's like the chai wala at Charminar who takes your money, gives you chai, and the transaction is done — he doesn't give you anything back (no receipt, no change, just chai and done). The interaction is complete, but there's no "return value" from your perspective.

never means "this function NEVER finishes — it goes on forever or throws an error." Control never returns to the caller because the function never reaches its end. It's like the endless traffic jam at Begumpet flyover during rush hour — you enter, but you NEVER come out. Or like the line at the biryani counter on Eid — some people say it never ends! Another example: an auto driver who says "meter nahi chalega" and then drives in circles forever — you NEVER reach your destination.

Understanding this distinction is crucial because TypeScript uses these types to catch bugs in your control flow, ensure exhaustive checks, and correctly type callbacks and error handlers. Mixing them up leads to confusing type errors and subtle bugs.

02void — Returns Nothing

void is the return type for functions that don't return a value. It's the most common return type for functions that do work but don't produce a result for the caller to use.

function log(message: string): void {
  console.log(message);
}

function saveToDatabase(data: object): void {
  // saves data, doesn't return anything
  db.save(data);
}

function handleClick(event: MouseEvent): void {
  // handles the click, no return value needed
  updateUI(event.target);
}

The function runs, does its work, and control returns to the caller — but without any value. In JavaScript, a function without a return statement implicitly returns undefined. But in TypeScript, void and undefined are different things!

function log(msg: string): void {
  console.log(msg);
}

const result = log("hello"); // result is void, NOT undefined
// You can't do: const x: undefined = log("hello"); // ❌ ERROR!

Practical uses of void include logging functions, event handlers like button.onclick = (e): void => { ... }, and side-effect-only functions like saving to a database, sending emails, or updating the DOM.

There's a special rule for callbacks: TypeScript allows returning a value from a void callback — the return value is simply ignored. This is specifically designed for callback patterns:

type Callback = () => void;

const fn: Callback = () => "hello"; // ✅ VALID!
// TypeScript ignores the return value in callback context

const items = [1, 2, 3];
items.forEach((item): void => {
  // Even if this returns something, forEach ignores it
  return item * 2; // Valid but ignored
});

This flexibility exists so you can pass more specific functions (that return values) where void callbacks are expected, making APIs like Array.forEach much easier to use.

03never — Never Returns

never represents a value that NEVER occurs. It's not "nothing" — it's "impossible." A function with return type never must either throw an error or enter an infinite loop. It never reaches its end, so it never produces a return value.

// 1. Throws an error
function fail(msg: string): never {
  throw new Error(msg);
}

// 2. Infinite loop
function infinite(): never {
  while (true) {}
}

// 3. Unreachable code
function unreachable(): never {
  return fail("This is unreachable");
}

TypeScript infers never automatically for code paths that can never complete. The most common practical use is exhaustiveness checking:

function assertUnreachable(x: never): never {
  throw new Error("Didn't expect: " + x);
}

type Shape = "circle" | "square";

function getArea(shape: Shape): number {
  switch (shape) {
    case "circle": return Math.PI * r * r;
    case "square": return s * s;
    default:
      return assertUnreachable(shape); // If we add a new shape, TS warns us!
  }
}

Another key use is type narrowing. When TypeScript narrows a type to nothing, the remaining type becomes never:

function f(x: string | number) {
  if (typeof x === "string") {
    // x is string
  } else if (typeof x === "number") {
    // x is number
  } else {
    // x is never — there's no other possibility!
    x; // type: never
  }
}

As the bottom type, never is assignable to EVERY type (it's at the bottom of the type hierarchy), but NO type is assignable to never (except never itself). This is because never never actually exists at runtime, so it's "safe" to treat it as any type.

04void vs never Showdown

Let's put void and never head to head. The key distinction is simple: void means "returns, but with no value." never means "doesn't return AT ALL."

// void — function completes, returns undefined
function log(): void {
  console.log("done");
  // implicit return undefined
}

// never — function throws, never reaches the end
function crash(): never {
  throw new Error("boom");
}

// never — function loops forever, never reaches the end
function loop(): never {
  while (true) { /* stuck forever */ }
}

The type system treats them very differently. void is a real type that represents the absence of a meaningful return value. never is the "bottom type" — it represents impossibility.

Because never never produces a value at runtime, it's safe to assign it to any type. Think about it: if a function never returns, it never produces a value that could conflict with the expected type:

let x: string = "hello";

function crash(): never {
  throw new Error("oops");
}

x = crash(); // ✅ Valid! crash() never returns a value,
             // so it can't produce a non-string

A practical pattern: a function that sometimes returns and sometimes throws:

function process(input: string): string | never {
  if (input.length === 0) {
    throw new Error("Empty input!");
  }
  return input.toUpperCase();
}

Here's the magic: string | never simplifies to just string. Why? Because never contributes nothing to a union — it represents an impossible case. It's like saying "I'll give you biryani OR nothing at all (and then I vanish)" — you're effectively just getting biryani. The impossible option doesn't change the menu.

type A = string | never;   // string
type B = number | never;   // number
type C = void | never;     // void
// never disappears from all unions!
05void & never Cheatsheet

Let's lock this in with a side-by-side cheatsheet. This is your quick reference for when to use void and when to use never.

Comparison Table

Featurevoidnever
MeaningReturns nothingNever returns at all
Function completes?✅ Yes❌ No (throws or loops)
Runtime return valueundefinedNever produced
Use casesLogging, side effects, callbacksError throwing, exhaustive checks
Type hierarchyRegular typeBottom type (assignable to everything)
In unionsStays as voidDisappears (T | never = T)
Can be assigned other types?No (except undefined in some contexts)No (only never assigns to never)
Can be assigned to other types?NoYes (assignable to everything)

Quick Syntax Reference

// void — side effects
function log(msg: string): void { console.log(msg); }
function save(data: object): void { db.save(data); }
function onClick(cb: () => void): void { ... }

// never — impossible outcomes
function fail(msg: string): never { throw new Error(msg); }
function infinite(): never { while(true) {} }
function assertUnreachable(x: never): never { throw new Error(); }

The Golden Rule

"void is the chai wala who finishes the job and walks away. never is the Begumpet traffic jam — you enter, but you never leave. Know the difference, bhai!"

If your function reaches its end normally but has nothing to return, use void. If your function throws or loops forever and never reaches its end, use never. And if you see never inferred somewhere unexpected, TypeScript is telling you that code path is impossible — pay attention, it's trying to help you catch a bug!

Key Takeaways

  • void means the function completes normally but returns no value (returns undefined at runtime)
  • never means the function never completes — it throws an error or loops forever
  • void is for side effects: logging, DOM updates, database saves, event handlers
  • never is for impossible states: error functions, exhaustive checks, unreachable code
  • never is the bottom type — assignable to every type, but nothing is assignable to never
  • never disappears from unions: string | never = string
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase