// Leverages TypeScript type system for safer, more maintainable code
# TypeScript Best Practices
Leverage TypeScript's type system to catch errors early and improve code quality.
## Enable Strict Mode
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
```
## Use Explicit Types for Function Returns
```typescript
// Bad - inferred return type
function getUser(id: number) {
return users.find(u => u.id === id);
}
// Good - explicit return type
function getUser(id: number): User | undefined {
return users.find(u => u.id === id);
}
```
## Avoid any, Use unknown
```typescript
// Bad
function processData(data: any) {
return data.value; // No type safety
}
// Good
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value;
}
throw new Error('Invalid data');
}
```
## Use Type Guards
```typescript
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
// Type guard function
function isCat(pet: Cat | Dog): pet is Cat {
return 'meow' in pet;
}
function makeSound(pet: Cat | Dog) {
if (isCat(pet)) {
pet.meow(); // TypeScript knows it's a Cat
} else {
pet.bark(); // TypeScript knows it's a Dog
}
}
```
## Use Discriminated Unions
```typescript
type Success = {
status: 'success';
data: User;
};
type Error = {
status: 'error';
message: string;
};
type Result = Success | Error;
function handleResult(result: Result) {
if (result.status === 'success') {
console.log(result.data); // TypeScript knows data exists
} else {
console.log(result.message); // TypeScript knows message exists
}
}
```
## Use Readonly for Immutability
```typescript
interface User {
readonly id: number;
readonly email: string;
name: string;
}
const user: User = { id: 1, email: 'test@example.com', name: 'Alice' };
user.name = 'Bob'; // OK
user.id = 2; // Error: Cannot assign to 'id'
// Readonly arrays
const numbers: readonly number[] = [1, 2, 3];
numbers.push(4); // Error
// Or
const numbers: ReadonlyArray<number> = [1, 2, 3];
```
## Use Utility Types
```typescript
interface User {
id: number;
name: string;
email: string;
password: string;
}
// Partial - all properties optional
type PartialUser = Partial<User>;
// Pick - select specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Omit - exclude specific properties
type UserWithoutPassword = Omit<User, 'password'>;
// Required - all properties required
type RequiredUser = Required<Partial<User>>;
// Record - create object type
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;
```
## Use never for Exhaustive Checks
```typescript
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.size ** 2;
case 'triangle':
return (shape.base * shape.height) / 2;
default:
// Ensures all cases are handled
const _exhaustive: never = shape;
throw new Error(`Unhandled shape: ${_exhaustive}`);
}
}
```
## Use Generics for Reusability
```typescript
// Bad - repeated code
function wrapInArray(value: string): string[] {
return [value];
}
function wrapNumberInArray(value: number): number[] {
return [value];
}
// Good - generic
function wrapInArray<T>(value: T): T[] {
return [value];
}
// Usage
const strings = wrapInArray('hello'); // string[]
const numbers = wrapInArray(42); // number[]
// Generic constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
```
## Use Const Assertions
```typescript
// Without const assertion
const config = {
endpoint: 'https://api.example.com',
timeout: 3000
};
// Type: { endpoint: string; timeout: number }
// With const assertion
const config = {
endpoint: 'https://api.example.com',
timeout: 3000
} as const;
// Type: { readonly endpoint: "https://api.example.com"; readonly timeout: 3000 }
// For arrays
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // 'red' | 'green' | 'blue'
```
## Avoid Type Assertions, Use Type Guards
```typescript
// Bad - type assertion
function processValue(value: unknown) {
const str = value as string;
return str.toUpperCase(); // Runtime error if not a string
}
// Good - type guard
function processValue(value: unknown) {
if (typeof value !== 'string') {
throw new Error('Value must be a string');
}
return value.toUpperCase(); // Safe
}
```
## Use Template Literal Types
```typescript
type Color = 'red' | 'green' | 'blue';
type Shade = 'light' | 'dark';
// Combine types
type ColorShade = `${Shade}-${Color}`;
// 'light-red' | 'light-green' | 'light-blue' |
// 'dark-red' | 'dark-green' | 'dark-blue'
```
## Use Conditional Types
```typescript
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Practical example
type NonNullable<T> = T extends null | undefined ? never : T;
type C = NonNullable<string | null>; // string
```
## Use Index Signatures Carefully
```typescript
// Be specific when possible
interface StringMap {
[key: string]: string;
}
// Better - known keys + index signature
interface Config {
apiUrl: string;
timeout: number;
[key: string]: string | number; // Additional properties
}
// Best - use Record when appropriate
type StringRecord = Record<string, string>;
```
## Use Enums or Const Objects
```typescript
// Enum (generates runtime code)
enum Status {
Pending = 'PENDING',
Active = 'ACTIVE',
Inactive = 'INACTIVE'
}
// Const object (no runtime cost)
const Status = {
Pending: 'PENDING',
Active: 'ACTIVE',
Inactive: 'INACTIVE'
} as const;
type StatusValue = typeof Status[keyof typeof Status];
```
## Use Non-Null Assertion Sparingly
```typescript
// Bad - hiding potential null
function getUser(id: number) {
return users.find(u => u.id === id)!; // Assumes always found
}
// Good - handle null case
function getUser(id: number): User {
const user = users.find(u => u.id === id);
if (!user) {
throw new Error('User not found');
}
return user;
}
```
## Prefer Interface Over Type for Objects
```typescript
// Use interface for object shapes
interface User {
id: number;
name: string;
}
// Extend interface
interface AdminUser extends User {
permissions: string[];
}
// Use type for unions, primitives, etc.
type ID = string | number;
type Status = 'active' | 'inactive';
```
TypeScript's type system prevents bugs before they reach runtime.6 matches