跪拜 Guibai
← All articles
Frontend · JavaScript · TypeScript

The 8 TypeScript Anti-Patterns That Keep Surviving Code Review

By kyriewen ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

TypeScript's value collapses when developers override it with `any`, `as`, and disabled strict checks. Each shortcut saves seconds during authoring but costs minutes — sometimes hours — during debugging and onboarding, and the patterns compound across a team.

Summary

Code reviews across three new team members surfaced the same patterns: `any` used to silence type errors, `as` assertions standing in for runtime validation, and optional chaining stretched across entire property chains. Each anti-pattern represents a moment where TypeScript's safety net was deliberately bypassed, leaving the next developer to guess at data shapes and nullability.

Fixes are straightforward but require discipline. Replacing `any` with `unknown` and type guards forces verification before access. Moving null checks to boundary layers — API responses, component props — keeps internal logic working with definite types. Union types replace most enums at zero runtime cost, and a team-wide rule on `interface` versus `type` removes pointless cognitive overhead.

The most damaging pattern is the one that looks harmless: `strict: false` or selectively disabling `strictNullChecks`. It tells the compiler to assume nothing is ever null, which is precisely when runtime crashes slip through. For legacy projects, `@ts-expect-error` annotations plus a TODO list beat leaving strict off indefinitely.

Takeaways
Every `any` annotation tells the compiler to stop checking; `unknown` plus a type guard achieves the same flexibility without sacrificing safety.
`as` assertions bypass compile-time verification entirely — a type guard or `instanceof` check validates assumptions at runtime instead.
Union types (`'active' | 'inactive'`) carry zero runtime overhead, unlike enums, which compile into IIFE objects and can introduce bidirectional mapping bugs with numeric values.
Optional chaining abused across long property chains turns every downstream variable into `string | undefined`, forcing null checks to spread through the entire codebase.
Mixing `interface` and `type` without a team rule creates inconsistency that slows reading; the choice matters less than having a shared convention.
Complex generic types used only once or twice are harder to read and debug than writing the concrete type directly.
Disabling `strictNullChecks` — even inside an otherwise `strict: true` config — removes TypeScript's most important safety check by assuming no value is ever null or undefined.
Legacy projects can adopt strict mode incrementally by marking existing errors with `@ts-expect-error` and fixing them from a TODO list.
Conclusions

The patterns aren't knowledge gaps — they're time-pressure shortcuts. `any` and `as` are the fastest way to silence a red squiggle, and the cost is deferred to whoever touches the code next.

Optional chaining abuse is really a symptom of unclear ownership over data shapes. When no single layer validates structure, every consumer defensively adds `?.` and the uncertainty propagates.

The enum-versus-union-type debate is less about technical merit and more about whether a team values runtime objects or zero-cost types; most frontend code benefits from the latter.

TypeScript's strict family of flags is a ratchet: turning one off weakens the entire system, but turning them all on forces a level of rigor that catches bugs before they reach production.

Concepts & terms
Type guard
A runtime check (function or condition) that narrows a union type to a specific subtype, e.g., `typeof x === 'string'` or a custom `data is User` predicate, letting the compiler treat the value as that narrower type afterward.
unknown vs any
`unknown` is the type-safe counterpart to `any`: it accepts any value but forbids accessing properties or calling methods until a type guard or assertion narrows it. `any` disables all type checking on the value.
strictNullChecks
A TypeScript compiler option (part of `strict`) that makes `null` and `undefined` distinct types. When disabled, the compiler treats every type as if it can never be null, hiding potential runtime crashes.
Union type
A type formed by combining multiple literal or object types with `|`, such as `'small' | 'medium' | 'large'`. Unlike enums, union types are erased at compile time and produce no runtime code.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗