Interview Q&A / TypeScript

AdvancedIn-depth4 min readUpdated 2 Oct 2026

What is the never type in TypeScript, and how do you use it for exhaustive checks?

SHORT ANSWER

never is the type with no possible values. It is the return type of functions that never finish normally, like one that always throws, and it is what remains after every case of a union has been handled. Assigning the leftover value to never makes the compiler report any case you forgot.

Explain it simply

Think of a hostel warden ticking off students at night roll call. Each name on the list is ticked when the student is found. If, at the end, the "not yet found" list is empty, everyone is accounted for. If someone is still on it, the warden raises an alarm.

never is TypeScript's "nobody left" type. When you handle each member of a union one by one, TypeScript removes it from the remaining type. After the last case, the remaining type is never. If you write code that says "this must be never here", and someone later adds a new member to the union without handling it, the compiler raises the alarm.

Example

ts
type PaymentMethod =
  | { kind: "upi"; upiId: string }
  | { kind: "card"; last4: string }
  | { kind: "netbanking"; bank: string };

function assertNever(value: never): never {
  throw new Error(`Unhandled case: ${JSON.stringify(value)}`);
}

function describe(method: PaymentMethod): string {
  switch (method.kind) {
    case "upi":
      return `UPI ${method.upiId}`;
    case "card":
      return `Card ending ${method.last4}`;
    case "netbanking":
      return `Net banking via ${method.bank}`;
    default:
      return assertNever(method); // method is never here
  }
}

Now a teammate adds | { kind: "wallet"; provider: string } to PaymentMethod. Without touching describe, the build fails at assertNever(method) with an error saying the wallet type is not assignable to never. The bug is caught at compile time, not by a customer.

How it works

  • Bottom type: never is assignable to every type, but no type (other than never itself) is assignable to never. That is why assigning a leftover value to a never parameter is an error unless the leftover really is empty.
  • Narrowing: in the switch, each case removes one member from the union. In default, nothing is left, so method has type never.
  • Functions that never return: a function that always throws, or loops forever, has return type never. Because assertNever returns never, return assertNever(method) satisfies the string return type.
  • Impossible types: string & number is never, and an object property with conflicting types in an intersection becomes never.
  • In conditional types: never acts like an empty union, so it disappears. Exclude<T, U> is defined as T extends U ? never : T, which keeps only the members not in U.

assertNever also protects at runtime: if bad data from an API slips past the types, you get a clear error instead of undefined silently returned.

Real-world use

  • Reducers and state machines with an action.type or state.status field.
  • Mapping every enum or literal value to a label, icon or handler.
  • Library types: filtering keys with conditional types, such as getting only the keys of an object whose values are functions.

Common mistakes

  • Confusing never with void. A void function finishes and returns nothing useful; a never function does not finish normally at all.
  • Adding a default that returns a fallback value. It hides new cases instead of reporting them.
  • Using if chains with a final else that assumes the last case. A new member silently falls into that else; an explicit check plus assertNever catches it.
  • Writing the check as const x: never = value; and then disabling the "unused variable" lint rule. A small assertNever helper is clearer and also throws at runtime.

What to say in the interview

"never is the bottom type: it has no values, it is assignable to everything, and nothing else is assignable to it. It shows up as the return type of functions that always throw, as the result of impossible intersections like string & number, and as what is left after narrowing has removed every member of a union. I use that for exhaustive checks: in the default branch of a switch on a discriminated union, I pass the value to an assertNever function that takes never. If someone adds a new case and forgets to handle it, the build fails instead of the app misbehaving. It also throws at runtime as a safety net."

Follow-up questions

  • What is the difference between never, void and unknown?
  • How is Exclude implemented?
  • Why does string & number become never?
  • How does a Record over a literal union also enforce exhaustiveness?

Go deeper

Read these once the basics above feel easy.

Conditional types distribute over unions when the checked type is a bare type parameter: T extends U ? X : Y with T = A | B becomes (A extends U ? X : Y) | (B extends U ? X : Y), and any never results vanish from the union. To check whether a type itself is never, you must stop distribution by wrapping both sides in a tuple: type IsNever<T> = [T] extends [never] ? true : false;. Without the brackets, IsNever<never> gives never, because distributing over an empty union produces an empty union.

#never#exhaustive-checks#discriminated-unions#control-flow

Related questions

Practise more TypeScript questions

Test your recall with short answers, then return to the detailed explanations when you need context.

Open TypeScript quick answers →

Get new questions like this by email

One short email when something new and useful lands. No spam, unsubscribe in one tap.

By subscribing you agree to the Privacy Policy.