Stop Using any
- •
unknownforces you to narrow before use —anysilently breaks type safety - •API responses: type them or use
unknown, neverany - •When you don't know the type, that's
unknown, notany
Narrowing Failures
- •
filter(Boolean)doesn't narrow — use.filter((x): x is T => Boolean(x)) - •
Object.keys(obj)returnsstring[], notkeyof typeof obj— intentional, objects can have extra keys - •
Array.isArray()narrows toany[]— may need assertion for element type - •
inoperator narrows but only if property is in exactly one branch of union
Literal Type Traps
- •
let x = "hello"isstring— useconstoras constfor literal type - •Object properties widen:
{ status: "ok" }hasstatus: string— useas constor type annotation - •Function return types widen — annotate explicitly for literal returns
Inference Limits
- •Callbacks lose inference in some array methods — annotate parameter when TS guesses wrong
- •Generic functions need usage to infer —
fn<T>()can't infer, pass a value or annotate - •Nested generics often fail — break into steps with explicit types
Discriminated Unions
- •Add a literal
typeorkindfield to each variant — enables exhaustive switch - •Exhaustive check:
default: const _never: never = x— compile error if case missed - •Don't mix discriminated with optional properties — breaks narrowing
satisfies vs Type Annotation
- •
const x: Type = valwidens to Type — loses literal info - •
const x = val satisfies Typekeeps literal, checks compatibility — prefer for config objects
Strict Null Gotchas
- •Optional chaining
?.returnsundefined, notnull— matters for APIs expectingnull - •
??only catchesnull/undefined—||catches all falsy including0and"" - •Non-null
!should be last resort — prefer narrowing or early return
Module Boundaries
- •
import typefor type-only imports — stripped at runtime, avoids bundler issues - •Re-exporting types:
export type { X }— prevents accidental runtime dependency - •
.d.tsaugmentation: usedeclare modulewith exact module path