typescript · memo
In one line: A type is a set of values; assignability is subset, judged by
shape (structural), not by name. Unions must be narrowed along control flow
before use — and it is all erased at runtime. Soundness is traded for ergonomics
in known holes: any, as, method bivariance, readonly props.
Download PDF Print view LaTeX source
How it works
- Structural:
{x,y}fits wherever{x}is expected (Swift: nominal).private/#privatefields make a class effectively nominal. - Excess-property check — the exception: a fresh object literal assigned straight to a typed target may not carry unknown keys (typo catcher); via a variable it passes.
- interface vs type: interfaces merge (re-declare = augment globals, lib types);
extendsreports conflicts at the declaration and is cached by name (cheaper than big&). Onlytypedoes unions, tuples, mapped/conditional.&turns a conflicting prop intonever. - Narrowing:
typeof("object"includesnull!),instanceof(prototype chain),"k" in x, equality (=0pt= nullcatches null and undefined; tag compare), truthiness (also drops0,"",NaN). Assignments re-widen; a reassignedletloses narrowing in callbacks (5.4: kept after the last assignment). - Guards:
(x): x is Fish— the body is trusted, not checked.asserts x is T/asserts condnarrows after the call. 5.5 infers predicates:arr.filter(x =0pt> x !0pt=0pt= undefined). - Top/bottom:
unknowntakes anything, gives nothing until narrowed.never= empty set: assignable to all, nothing to it.anyswitches checking off and spreads.void= “result ignored”: a() =0pt> voidslot accepts() =0pt> number. - Widening:
const s = 'a'is'a';letand object props widen tostring.as const= deepreadonly+ literals. - Optionality:
a?: Tmay be absent;a: T|undefinedmust be present.exactOptionalPropertyTypesforbids writinga: undefinedintoa?: T. - Functions: fewer params is fine (
forEach(x =0pt> …)); return is covariant; params contravariant understrictFunctionTypes— except method syntaxm(x: T): void, kept bivariant (soDog[]fitsAnimal[]).
Example — discriminated union, guards
type Shape = // Swift: enum + associated values
| { kind: 'circle'; r: number }
| { kind: 'rect'; w: number; h: number };
function area(s: Shape): number {
switch (s.kind) { // the tag narrows the union
case 'circle': return Math.PI * s.r ** 2;
case 'rect': return s.w * s.h;
default: return s satisfies never; // new kind -> error
}
}
const isStr = (x: unknown): x is string => typeof x === 'string';
function check(c: unknown, msg: string): asserts c {
if (!c) throw new Error(msg);
}
annotation · satisfies · as
const r1: Record<string, string> = { home: '/' };
r1.hmoe; // compiles: the annotation IS the type now
const r2 = { home: '/' } satisfies Record<string, string>;
r2.hmoe; // error: inferred keys kept (TS 4.9)
const r3 = {} as Record<'home', string>; // compiles, lies
interface P { x: number }
const p: P = { x: 1, y: 2 }; // error: excess property
const t = { x: 1, y: 2 }; const q: P = t; // OK: structural
Swift → TS
| Swift | TypeScript |
|---|---|
| protocol conformance (nominal) | any matching shape (structural) |
| enum + associated values | union with a literal kind tag |
compiler-exhaustive switch | const x: never in default |
Optional<Optional<T>> nests | T|undefined|undefined collapses |
as! traps at runtime | as never checks anything |
Any | unknown (not any) |
Interview traps
{}means “any non-nullish value” (even5), not “empty object”;object= non-primitive; empty =Record<string, never>.{readonly x}is assignable to{x}(hole); butreadonly T[]is not assignable toT[].readonlyis shallow and compile-time only.- A wrong
x is Tguard is an unchecked lie — worse thanas, it hides at every call site. - Callbacks declared as methods are bivariant: write
onX: (e: E) =0pt> void(property) to get strict checks. if (count)also drops0: compare withundefined.asrefuses only types that don’t overlap at all;x as unknown as Tforces anything — grep for it in review.
Remember
Types are sets · assignable = subset · shape, not name · narrow before you touch.
Likely questions
typeorinterface? — interface for object contracts;typefor unions and computed types.unknownvsany? — both accept all;unknownforces a narrow before use,anysilently disables checking downstream.satisfiesvsas? — validates yet keeps the inferred type;asasserts, checked only for “sufficient overlap”.- Exhaustiveness? — assign the leftover to
never. - Why is an extra key rejected only sometimes? — the excess-property check fires on fresh literals only.