Every card belongs to one rule, and every rule has cards on both sides of it — the pairs that pass and the pairs that do not. The squares are yours: a card you last got right, one you last missed, one you have not met yet.
01Literals and primitives
A literal type is one value of its primitive, so it extends that primitive — never the reverse, and never across primitives: "42" is a string, not a number. A number literal is its value however it is spelled: 0x10 is 16.
"a" extends string
truestring extends "a"
TS2322
A union on the right is a choice: whatever extends one of its members extends the whole union. PropertyKey is string | number | symbol.
"a" extends "a" | "b"
true"c" extends "a" | "b"
TS2322
A union on the left has to fit as a whole: every member must extend the right side. boolean is nothing but true | false.
"a" | "b" extends string
true"a" | "b" extends "a"
TS2322
Under strictNullChecks, null and undefined are types of their own. They fit only where they are named — or unknown, any, and, for undefined alone, void.
Every type extends unknown. unknown itself extends only what admits every value: unknown, any — or {} | null | undefined, which is the same set spelled out.
string extends unknown
trueunknown extends string
TS2322
never is the type with no values, so it extends everything — and nothing extends it but never itself.
never extends string
truestring extends never
TS2322
07any switches the check off
Wherever any stands, that part of the comparison passes, in either direction. It does not change the structure around it, though: a missing property or a wrong tuple length still fails.
08{} is not an empty object
{} means “anything but null or undefined”, so primitives extend it; object shuts them out. Yet {} extends object while string extends {} — assignability is not transitive.
{ a: string } extends {}
truenull extends {}
TS2322
The capitalised names are interfaces for the wrapper objects. A primitive extends its wrapper, and any object type built from the wrapper’s members as the wrapper types them — a string’s length is a number, even for "abc". A wrapper never extends the primitive, and Object behaves like {}.
string extends String
trueString extends string
TS2322
Properties are compared one at a time, in the same direction as the whole: a narrower property type fits, a wider one does not.
12Optional is not | undefined
An optional property may be missing altogether; a: T | undefined has to be there. So required fits optional, and optional does not fit required.
A type whose properties are all optional is “weak”. Anything that has properties but shares none of them is rejected with TS2559 — even though, structurally, it would fit.
14readonly properties do not count
readonly on a property limits what you may do through that type, and assignability ignores it: { readonly a: string } and { a: string } extend each other.
An array of narrower elements extends an array of wider ones. That is unsound for writes — push a number into a string[] seen as unknown[] — and TypeScript allows it anyway.
16Readonly arrays stay readonly
A readonly array or tuple has no mutating methods, so it cannot fill a mutable slot (TS4104). A mutable array fits a readonly slot without complaint.
A tuple is an array whose length is part of its type — [1, 2] has length: 2. Tuples extend arrays of their elements; an array never extends a tuple, because it might have any length.
[string, number] extends (string | number)[]
true(string | number)[] extends [string, number]
TS2322
18Parameters run backwards
Under strictFunctionTypes a function extends another only if it accepts everything the other may be passed: parameter types are compared in reverse, and an optional parameter may be passed undefined. Accepting more is the safe direction.
19Fewer parameters is fine
JavaScript lets a function ignore its arguments, so one that takes fewer parameters extends one that takes more. Requiring an extra argument is what fails; a rest parameter counts as any number.
Return types are compared in the same direction as the functions: a function returning something narrower extends one returning something wider.
A function type returning void promises only that nobody reads the result, so a function returning anything extends it. void itself is neither undefined nor null: only void, unknown and any accept it.
Method syntax opts out of strictFunctionTypes: a method’s parameters are compared in both directions, and it is the target’s syntax that decides. A function-typed property gets the strict check. It is why Set<"a"> extends Set<string>.
23Function, generics and constructors
Every function extends Function, but Function promises no particular signature. A generic function extends each instantiation its constraint allows, not the reverse — and an abstract constructor cannot fill a slot that needs new.
A type with several call signatures extends a single signature if any one of its overloads fits it. The other way round, one signature has to fit every overload of the target, one at a time.
An index signature requires every property to fit it. An object type literal gets one implicitly; an interface does not, and neither does object — so neither extends Record<string, unknown>, while the same shape as a type alias does. A signature typed any is the exception: it takes any object.
A template literal type is a pattern over strings. ${string} matches any text, the empty string included; ${number} any non-empty string JavaScript reads as a finite number — "1e3" and "0x1F" yes, "Infinity" no — and ${bigint} only whole ones. Lowercase<string> and its kin match text already in that case.
An intersection has every member’s properties, so it extends each member. One that no value can inhabit — string & number, two different kind literals — reduces to never, and never extends everything.
An object extends a union of objects when it fits one of the members. Since TypeScript 3.5 an object whose discriminant is itself a union is split into one object per value, and each piece is checked — so { kind: "a" | "b" } extends { kind: "a" } | { kind: "b" }.
keyof is the union of a type’s known keys: numeric keys stay numbers, an array’s include every method name, and a string index signature’s are string | number. keyof {} and keyof unknown are never; keyof never is every key there is.
T[K] is the type of property K of T, and a union of keys gives a union of types. On an array, T[number] is the element type; on a tuple, the union of its elements, and T["length"] its literal length. An optional property brings its undefined with it.
31Utility types are just types
Partial, Required, Pick, Omit and Record resolve to ordinary object types, and are checked exactly like them — property by property.
32Computed types resolve first
ReturnType, Parameters, Awaited, Exclude, Extract and NonNullable are conditional types. The compiler resolves each to the plain type it produces before it compares anything — so ReturnType<() => void> is void, and NonNullable<unknown> is {}.
33Classes are structural — until private
Two classes with the same public shape extend each other, and a plain object type can extend a class; statics live on the constructor, so they do not count. A private, protected or #private member makes a class nominal: only its own declaration and its subclasses have that member.
A numeric enum still accepts any number, and any literal that is one of its values — but not an out-of-range literal. Each member extends its own value, so S.A extends "a"; yet a string enum accepts only its own members, and "a" is not S.A.
enum E { A, B }E.A extends number
trueenum E { A, B }E extends E.A
TS2322
A unique symbol is a literal of symbol: it extends symbol and PropertyKey, symbol does not extend it, and no two of them extend each other. The well-known symbols, like Symbol.iterator, are unique symbols too.
A generic type is compared through its type argument, in the direction the type uses it: properties and return types covariantly — mutable properties too — and parameters of function types contravariantly. in and out state the direction outright, overriding what the compiler would measure; in out pins it invariant.