I spent a year writing TypeScript like it was JavaScript with type annotations bolted on. Types were things you declared and then fought when they got in the way. Then I started actually reading the handbook.
Conditional types
T extends U ? X : Y sounds academic until you actually need it. I use this pattern constantly to unwrap Promises or extract element types from arrays:
type Awaited<T> = T extends Promise<infer U> ? U : T
type Item<T> = T extends (infer U)[] ? U : neverThe infer keyword is what makes this powerful — it lets TypeScript "capture" a type from within a structure while pattern-matching.
satisfies without losing narrowing
Before satisfies, I'd write as const and lose type checking, or add an explicit type and lose inference. satisfies gives you both:
const palette = {
red: [255, 0, 0],
green: "#00ff00",
} satisfies Record<string, string | number[]>
palette.red.map(...) // ✓ still inferred as number[]
palette.green.toUpperCase() // ✓ still inferred as stringThese aren't obscure edge-case features. Once you know them, you'll use them every week.