Type Inference¶
Rule¶
Let TypeScript infer your type
- Reduced redundancy - Type inference eliminates unnecessary type annotations, making code cleaner and less repetitive.
- Error prevention - When types are inferred, refactoring is safer because changing a variable's initialization automatically updates its type everywhere.
- Better readability - Code becomes more concise and easier to read when not cluttered with obvious type annotations.
- Future-proofing - Letting TypeScript infer types from libraries means your code adapts automatically when those libraries improve their type definitions.
- Maintainability - Less repeated type information means fewer places to update when types change.
Examples¶
🚨 DON’T¶
// Redundant primitive type annotations
const loading: boolean = false;
// Obvious object type annotations
const user: { id: number; name: string } = {
id: 1,
name: 'John Doe'
};
// Redundant service injection types
#router: Router = inject(Router);
// Over-annotated function parameters in callbacks
this.users$ = this.http.get<User[]>('/api/users');
this.users$.subscribe((users: User[]) => {
this.users = users;
});
// Explicit return types that match inference
const getFullName = (first: string, last: string): string => {
return `${first} ${last}`;
}
// Explicit types for computed properties
const isValid: boolean = user.email.includes('@');
✅ DO¶
// Clean primitive inferences
const loading = false;
// Simple object inference
const user = {
id: 1,
name: "John Doe",
};
// Clean service injection
#router = inject(Router);
// Streamlined Observable subscriptions
this.users$ = this.http.get<User[]>("/api/users");
this.users$.subscribe((users) => {
this.userList = users;
});
// Inferred return types
const getFullName = (first: string, last: string) => {
return `${first} ${last}`;
};
// Inferred computed properties
const isValid = user.email.includes("@");
Exceptions¶
// Function parameters (required)
const processUser = (user: User) => {
return user.name;
};
// Empty arrays that will be populated later
const users = [] as User[];
// Variables declared without initialization
let currentUser: User | null;
// When you need to widen or narrow a type
const status = "loading" as const; // Literal type instead of string
// Public API boundaries
export const calculateTotal = (items: CartItem[]): number => {
return items.reduce((sum, item) => sum + item.price, 0);
};
// Complex generic constraints
const merge = <T extends Record<string, unknown>>(
obj1: T,
obj2: Partial<T>,
): T => {
return {...obj1, ...obj2};
};
// When inference would be too broad
const config: AppConfig = {
apiUrl: process.env.API_URL || "http://localhost:3000",
};