IsNever
IsNever
Challenge
Implement a type IsNever, which takes an input type T.
If the type resolves to never, return true, otherwise false.
type A = IsNever<never> // expected to be true
type B = IsNever<undefined> // expected to be false
type C = IsNever<null> // expected to be false
type D = IsNever<[]> // expected to be false
type E = IsNever<number> // expected to be falseSolution
type IsNever<T> = [T] extends [never] ? true : falseOne line. Deceptively simple. The magic is all in those square brackets.
Deep Dive
Why can't we just write T extends never?
// ❌ Broken
type IsNeverWrong<T> = T extends never ? true : false
type Test = IsNeverWrong<never> // never, not true!The result is never, not true. What's going on?
Distributive conditional types strike again
When you write T extends Constraint ? A : B with a naked type parameter, TypeScript treats unions specially. It distributes the condition over each member of the union:
type Distributed = (string | number) extends string ? 'yes' : 'no'
// Becomes:
// (string extends string ? 'yes' : 'no') | (number extends string ? 'yes' : 'no')
// = 'yes' | 'no'Now here's the catch: never is the empty union — a union with zero members. When you distribute over nothing, you get nothing back:
// T = never (empty union)
// Distributing: ... over zero members ...
// Result: never (union of zero results)The conditional is never evaluated at all. TypeScript short-circuits and returns never.
The tuple wrapper trick
Wrapping T in a tuple [T] disables distribution:
type IsNever<T> = [T] extends [never] ? true : falseNow [T] is not a naked type parameter — it's a tuple type containing T. TypeScript doesn't distribute over tuple types, so the condition is evaluated exactly once:
- When
T = never:[never] extends [never]→true✓ - When
T = string:[string] extends [never]→false✓
Why does [never] work but never doesn't?
| Expression | Behavior |
|---|---|
never extends never | Distribution over empty set → never |
[never] extends [never] | Single comparison → true |
The tuple acts as a container that "solidifies" the type, preventing the special empty-union behavior.
Common patterns that use this trick
// Detect never
type IsNever<T> = [T] extends [never] ? true : false
// Prevent distribution over any union
type NoDistribute<T> = [T] extends [T] ? T : never
// Safe comparison without distribution
type Equals<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : falseRelated: the any trap
Note that [any] extends [never] is boolean, not true or false:
type Test = [any] extends [never] ? true : false // booleanThis is because any is both assignable and not assignable to everything — TypeScript hedges and returns both branches as a union. If you need IsNever that also rejects any, you'd need an additional check:
type IsStrictNever<T> = [T] extends [never]
? (0 extends (1 & T) ? false : true) // reject any
: falseBut for most use cases, the simple [T] extends [never] is sufficient.
Key Takeaways
neveris the empty union — distributing over it yieldsnever, not a boolean- Tuple wrapping
[T]disables distributive behavior, allowing normal comparison [T] extends [never]is the canonical way to detectneverin TypeScript- Watch out for
any— it makesextendschecks returnboolean(both branches) - This pattern appears everywhere:
IsNever,IsAny,IsUnion,Equals, etc.
