Skip to content
webtype.orgTwelve concepts · 12/12

Concept 12 of 12

Exact equality

Every check on this site tests exact type identity rather than assignability. That choice is why an answer can be *usable* and still fail, and understanding it is the difference between fighting the checker and reading it.

Why `extends` is not enough

type Inter = { a: string } & { b: number }
type Flat = { a: string; b: number }

type OneWay = Inter extends Flat ? true : false   // true
type Other = Flat extends Inter ? true : false    // true
// Mutually assignable — yet they are not the same type.

Mutual assignability is weaker than identity. An intersection and the flattened object accept each other, but they are structurally different, and a checker built on `extends` would call a half-finished answer correct.

The Equal trick

type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false

Two deferred conditionals are only assignable to one another when the compiler resolves them identically — which it does only for genuinely identical types. It is an internal implementation detail rather than a documented feature, and it is the closest thing to type equality TypeScript exposes.

Flattening an intersection

type Prettify<T> = { [K in keyof T]: T[K] } & {}

type Fixed = Prettify<{ a: string } & { b: number }>
// { a: string; b: number } — now Equal passes

Mapping over the accumulated keys produces a fresh object type. The trailing `& {}` is what forces TypeScript to actually evaluate the mapping instead of keeping it lazy, which is why the idiom looks like a typo and is not.

The common wrong answer

// Intent: does this tuple contain exactly `true`?
type Includes<T extends unknown[], U> = U extends T[number] ? true : false

Includes<[boolean], true>   // true — but the tuple holds boolean, not true

`extends` asks whether `true` fits into `boolean`, and it does. Membership is not assignability, and whenever a problem says "exactly", `extends` is the wrong instrument.

Takeaway

Assignable is not equal. When a check rejects an answer that clearly works, the usual culprit is an intersection that never got flattened.