TypeScript narrows JavaScript's bug surface dramatically — null-safety, exhaustiveness, refactor confidence — but the gap between the type model and runtime behavior is where bugs hide. An `any` that escapes the typecheck silently disables every guarantee downstream of it.
The recurring shapes: stale closures in React's hook system that the type system can't see, `as Foo` casts that paper over real shape mismatches, generic functions that accept `unknown` and treat it as `Foo` without runtime parsing, and `JSON.parse` results typed as the eventual schema without validation in between.
The defense is to treat the boundary between trusted and untrusted code as a parsing problem. Parse, don't validate. A well-placed `zod`/`valibot`/`io-ts` schema turns the run-time guarantee into a compile-time guarantee.
Common pitfalls
Stale closures in `useEffect`
An empty dep array captures the initial values forever. The functional updater (`setState(prev => ...)`) avoids the trap.
Lying type assertions
`as Foo` is the compiler agreeing not to check. If the runtime shape doesn't match, the bug ships and nothing flags it.
Unparsed JSON
`JSON.parse(s) as User` is a lie. Use a schema parser to convert untrusted JSON to typed values.