Skip to content
webtype.orgwebtype.orgTwelve concepts · 12/12

    ↑↓ move · ⏎ open · esc close

    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.

    Exercise

    Answer `true` only when both sides are the same type — an intersection is not a flat object.

    Try it

    1
    • Flatten<{ a: 1 } & { b: 2 }>
      { a: 1; b: 2 }

    Takeaway

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