跪拜 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 ↗