TypeScript Best Practices for 2025
TypeScript has become the de facto standard for building robust JavaScript applications. Let's explore the best practices that will make you a TypeScript expert.
TL;DR
Essential TypeScript practices:
- Always enable strict mode
- Use type inference when possible
- Avoid
anytype at all costs - Leverage utility types effectively
- Write clear, self-documenting interfaces
Why TypeScript?
TypeScript provides several key benefits:
Type Safety
Catch errors before runtime:
// ❌ JavaScript - Runtime error
function greet(name) {
return `Hello, ${name.toUpperCase()}`;
}
greet(123); // Runtime error!
// ✅ TypeScript - Compile-time error
function greet(name: string): string {
return `Hello, ${name.toUpperCase()}`;
}
// greet(123); // Error: Argument of type 'number' is not assignable to parameter of type 'string'
Better IDE Support
Get intelligent code completion and refactoring tools that understand your code structure.
Documentation
Types serve as inline documentation for your code.
Essential Configuration
Always use strict mode in tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
Type System Best Practices
Use Type Inference
Let TypeScript infer types when obvious:
// ❌ Unnecessary type annotation
const count: number = 5;
const name: string = "John";
// ✅ Let TypeScript infer
const count = 5;
const name = "John";
Avoid Any
The any type defeats the purpose of TypeScript:
// ❌ Bad - loses type safety
function process(data: any) {
return data.value;
}
// ✅ Good - use generics or specific types
function process<T extends { value: string }>(data: T) {
return data.value;
}
Leverage Utility Types
TypeScript provides powerful utility types:
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Make all properties optional
type PartialUser = Partial<User>;
// Make all properties required
type RequiredUser = Required<User>;
// Pick specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Omit specific properties
type UserWithoutEmail = Omit<User, 'email'>;
// Make all properties readonly
type ReadonlyUser = Readonly<User>;
Advanced Patterns
Discriminated Unions
Create type-safe state machines:
type LoadingState = {
status: 'loading';
};
type SuccessState<T> = {
status: 'success';
data: T;
};
type ErrorState = {
status: 'error';
error: Error;
};
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;
function handleState<T>(state: AsyncState<T>) {
switch (state.status) {
case 'loading':
return 'Loading...';
case 'success':
return state.data; // TypeScript knows data exists here
case 'error':
return state.error.message; // TypeScript knows error exists here
}
}
Branded Types
Create nominal types for better type safety:
type UserId = string & { readonly __brand: unique symbol };
type ProductId = string & { readonly __brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
function createProductId(id: string): ProductId {
return id as ProductId;
}
function getUser(id: UserId) {
// Implementation
}
const userId = createUserId("user-123");
const productId = createProductId("product-456");
getUser(userId); // ✅ Works
// getUser(productId); // ❌ Error: ProductId is not assignable to UserId
Common Pitfalls
Implicit Any
// ❌ Bad - implicit any
function map(arr, fn) {
return arr.map(fn);
}
// ✅ Good - explicit types
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn);
}
Type Assertions
Use type assertions sparingly:
// ❌ Bad - unsafe assertion
const value = getSomeValue() as string;
// ✅ Better - use type guards
function isString(value: unknown): value is string {
return typeof value === 'string';
}
const value = getSomeValue();
if (isString(value)) {
// TypeScript knows value is a string here
}
Conclusion
TypeScript is a powerful tool that can significantly improve your code quality and developer experience. By following these best practices, you'll write more maintainable, type-safe code that catches bugs before they reach production.
Remember: TypeScript is there to help you, not hinder you. Embrace its features, and you'll wonder how you ever lived without it!
Last updated: January 5, 2025
