Reference · annotations, assertions, structural typing
Annotations, assertions & structural typing — decoder
Where a type comes from, how you attach it, and why a value is (or isn't) accepted.
Find the source first; the behavior follows.
Three sources of a type (0.2)
| source | who supplies it | how you spot it |
| annotation | you, explicitly | a : then a type you typed |
| inference | the compiler, from a value | no : — type came from the = |
| definition | a name you declared once | type / interface / .d.ts |
Maxim: annotation = a promise you made · inference = the
compiler's guess from the value · definition = a reusable name that annotates nothing
until attached. “Type information” is the umbrella — it covers annotated and inferred types.
interface User { id: number; name: string; } // definition (named, reusable)
const u: User = { id: 1, name: "Ada" }; // annotation (you wrote : User)
let n = 5; // inference (n : number, derived from 5)
let mode = "dark"; // inferred string — let widens the literal
const theme = "dark"; // inferred "dark" — const keeps the literal
Attach a type three ways: annotation vs as vs satisfies (0.3)
| syntax | can add props later? | keeps literal types? | use case |
| const o: Type = … | yes | no | add properties later |
| const o = … as Type | yes | no | add properties later |
| const o = … satisfies Type | no | yes | check a literal at creation |
Annotation & as → variable becomes the wide declared type
(room to grow, literals lost). satisfies → checks the value, keeps its
narrow literal type (no room to grow, detail kept). as can lie;
satisfies cannot — as asserts without checking, satisfies
only verifies.
const a: Record<string, number> = {}; a.x = 1; // OK — wide Record type
const b = {} as Record<string, number>; b.x = 1; // OK — wide, by assertion
const c = {} satisfies Record<string, number>; c.x = 1;// ERR — c is still {}, narrow
Structural typing & the excess-property rule (0.4)
| how the value arrives | check applied | extra props? |
| object literal passed inline | excess-property check (typo guard) | rejected |
| literal stored in a variable first | plain structural subtyping | allowed |
TS is structural (duck typing): a value fits a type if it has the required
shape, extras allowed. A fresh literal handed straight to a slot gets a one-time
excess-property check that flags unknown keys as likely typos. Inline ≈ strict
assignment; variable ≈ subtyping (a richer subtype substitutes for the base).
interface FetchOptions { url: string; method: string; }
const myFetch = (o: FetchOptions) => {};
myFetch({ url: "/", method: "GET", search: x }); // ERR — inline: excess 'search'
const options = { url: "/", method: "GET", search: x };
myFetch(options); // OK — variable: subtype, extras fine
The 3-second checks
- Where's the type from? Colon you wrote → annotation · no colon → inference ·
type/interface → definition.
- Wide or narrow?
: Type / as Type → wide declared type ·
satisfies Type → narrow literal, checked.
- Why accepted/rejected? Inline literal → excess check (extras error) · via variable →
structural subtyping (extras fine).