TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. It adds static types to JavaScript, helping catch errors early and making code more maintainable.
TypeScript Anti-Patterns Overview
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.
Overusing any Type
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 Without Verification
Type assertions (as
keyword) tell the compiler to trust you without verification. Use type guards to validate data at runtime.
Not Using readonly for Immutable Properties
Use the readonly
modifier for properties that shouldn’t change after initialization to prevent accidental mutations.
Using Object as a Type
The Object
type is too general and doesn’t provide useful type information. Use more specific types or generics.
Not Leveraging Discriminated Unions
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.
Using Function Types Instead of Interfaces
For complex function types, especially those with additional properties, use interfaces instead of simple function types.
Not Using Strict Null Checks
Always enable strictNullChecks
in your TypeScript configuration to catch null and undefined errors at compile time.
Using String Enums Instead of Literal Types
For simple string constants, string literal unions are often more appropriate than enums, as they result in less code and are more straightforward.
Not Using Index Signatures Properly
When using index signatures, make sure they’re compatible with all properties in the interface.
Not Using Utility Types
TypeScript provides many built-in utility types like Partial
, Readonly
, Pick
, Omit
, etc. Use them instead of manually creating derived types.
Using namespace Instead of ES Modules
Namespaces are an older way of organizing code in TypeScript. Modern TypeScript code should use ES modules (import/export) instead.
Not Using Type Guards
Use type guards (typeof
, instanceof
, or custom predicates) to narrow types safely instead of type assertions.
Not Using Interfaces for Public APIs
Interfaces are generally preferred for public APIs because they can be extended later without breaking changes, while types cannot.
Using the Non-null Assertion Operator (!.)
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.
Not Using Unknown for API Responses
Use unknown
instead of any
for values from external sources like API responses, then use type guards to narrow the type safely.
Not Using Branded Types for IDs
Branded types (also called nominal types) add type safety to primitive types like strings and numbers, preventing accidental use of the wrong ID type.
Not Using Exhaustiveness Checking
Exhaustiveness checking ensures that all possible values of a union type are handled, catching errors when new values are added to the union.
Not Using Proper Module Augmentation
When extending third-party types, use proper module augmentation instead of trying to modify the original types directly.
Not Using Conditional Types
Conditional types allow you to create flexible, reusable type definitions that depend on the properties of input types.