Skip to main content
TypeScript, while improving upon JavaScript with static typing, still has common anti-patterns that can lead to bugs, performance issues, and maintenance problems. Here are the most important anti-patterns to avoid when writing TypeScript code.
Using any defeats the purpose of TypeScript’s type system. It bypasses type checking and can lead to runtime errors that TypeScript is designed to prevent.
Type assertions (as keyword) tell the compiler to trust you without verification. Use type guards to validate data at runtime.
Use the readonly modifier for properties that shouldn’t change after initialization to prevent accidental mutations.
The Object type is too general and doesn’t provide useful type information. Use more specific types or generics.
Discriminated unions (also called tagged unions) make working with union types safer and more maintainable by adding a common property that can be used to distinguish between types.
For complex function types, especially those with additional properties, use interfaces instead of simple function types.
Always enable strictNullChecks in your TypeScript configuration to catch null and undefined errors at compile time.
For simple string constants, string literal unions are often more appropriate than enums, as they result in less code and are more straightforward.
When using index signatures, make sure they’re compatible with all properties in the interface.
TypeScript provides many built-in utility types like Partial, Readonly, Pick, Omit, etc. Use them instead of manually creating derived types.
Namespaces are an older way of organizing code in TypeScript. Modern TypeScript code should use ES modules (import/export) instead.
Use type guards (typeof, instanceof, or custom predicates) to narrow types safely instead of type assertions.
Interfaces are generally preferred for public APIs because they can be extended later without breaking changes, while types cannot.
The non-null assertion operator (!) tells TypeScript to ignore the possibility of null or undefined. This can lead to runtime errors if the value is actually null.
Use unknown instead of any for values from external sources like API responses, then use type guards to narrow the type safely.
Branded types (also called nominal types) add type safety to primitive types like strings and numbers, preventing accidental use of the wrong ID type.
Exhaustiveness checking ensures that all possible values of a union type are handled, catching errors when new values are added to the union.
When extending third-party types, use proper module augmentation instead of trying to modify the original types directly.
Conditional types allow you to create flexible, reusable type definitions that depend on the properties of input types.