TypeScript Best Practices
Comprehensive guide to writing clean, type-safe, and maintainable TypeScript code.
When to Use
- Configuring a new TypeScript project
- Deciding between interface vs type alias
- Writing async/await code
- Reviewing TypeScript code quality
- Avoiding common TypeScript pitfalls
Quick Reference
// Type inference - let TS do the work
const name = 'Alice';
// Explicit for APIs
function greet(name: string): string { ... }
// Unknown over any
function safe(data: unknown) { ... }
// Type-only imports
import type { User } from './types';
// Const assertions
const tuple = [1, 2] as const;
// Null safety
const len = str?.length ?? 0;
// Guard clauses
if (!valid) throw new Error();
// main logic...
Common Mistakes
| Mistake | Problem | Solution |
|---|
Overusing any | Defeats type checking | Use unknown, generics, or proper types |
| Not using strict mode | Misses many errors | Enable "strict": true |
| Redundant annotations | Clutters code | Trust type inference |
| Ignoring union types | Runtime errors | Use type guards |
| Not handling null | Crashes | Use ?. and ?? operators |
| Nested conditionals | Hard to read | Use guard clauses |
| Duplicate types with Zod | Maintenance burden | Infer from z.infer<typeof schema> |
| Sequential awaits for independent ops | Slower execution | Use Promise.all |
| Non-Error cause | Breaks error chains | Always use Error instance for cause |
Progressive Disclosure
Key Principles
- Type inference when obvious - Let TypeScript infer simple types
- Explicit for public APIs - Document function signatures clearly
- Unknown over any - Use
unknown with type guards instead of any
- Guard clauses - Early returns reduce nesting
- Type-only imports - Better tree-shaking with
import type
References