TypeScript Tips and Tricks for Better Code
TypeScript has become an essential tool for JavaScript developers. Here are some practical tips and advanced patterns that will help you write better, more type-safe code.
Utility Types
TypeScript comes with many built-in utility types that can save you time and make your code more expressive.
Pick and Omit
Extract or exclude properties from existing types:
interface User {
id: string
name: string
email: string
password: string
createdAt: Date
}
// Create a public user type without sensitive data
type PublicUser = Omit<User, 'password'>
// Create a type for user creation
type CreateUser = Pick<User, 'name' | 'email' | 'password'>
Partial and Required
Make properties optional or required:
// Make all properties optional for updates
type UpdateUser = Partial<User>
// Make specific properties required
type UserWithRequiredEmail = Required<Pick<User, 'email'>> & Partial<User>
Generic Constraints
Use constraints to make your generics more specific:
// Constraint to objects with an id property
interface HasId {
id: string
}
function updateEntity<T extends HasId>(entity: T, updates: Partial<T>): T {
return { ...entity, ...updates }
}
// This ensures you can only update entities that have an id
const updatedUser = updateEntity(user, { name: 'New Name' })
Discriminated Unions
Create type-safe state machines:
type LoadingState = {
status: 'loading'
}
type SuccessState = {
status: 'success'
data: any
}
type ErrorState = {
status: 'error'
error: string
}
type AsyncState = LoadingState | SuccessState | ErrorState
function handleState(state: AsyncState) {
switch (state.status) {
case 'loading':
// TypeScript knows there's no data or error here
return 'Loading...'
case 'success':
// TypeScript knows data is available
return state.data
case 'error':
// TypeScript knows error is available
return `Error: ${state.error}`
}
}
Mapped Types
Transform existing types programmatically:
// Make all properties readonly
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
// Make all properties optional
type Optional<T> = {
[P in keyof T]?: T[P]
}
// Create event handlers for form fields
type FormHandlers<T> = {
[K in keyof T as `handle${Capitalize<string & K>}Change`]: (value: T[K]) => void
}
type UserFormHandlers = FormHandlers<User>
// Results in:
// {
// handleIdChange: (value: string) => void
// handleNameChange: (value: string) => void
// handleEmailChange: (value: string) => void
// }
Template Literal Types
Create precise string types:
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type Version = 'v1' | 'v2'
type APIEndpoint = `/${Version}/${string}`
// Type-safe API route function
function apiCall<T>(method: HTTPMethod, endpoint: APIEndpoint): Promise<T> {
// Implementation
}
// Usage - TypeScript ensures the endpoint format is correct
apiCall('GET', '/v1/users') // ✅ Valid
apiCall('GET', '/users') // ❌ Invalid - missing version
Conditional Types
Create types that depend on conditions:
type ApiResponse<T> = T extends string
? { message: T }
: T extends object
? { data: T }
: never
type StringResponse = ApiResponse<string> // { message: string }
type ObjectResponse = ApiResponse<User> // { data: User }
Best Practices
1. Use unknown
instead of any
// Bad
function parseJSON(json: string): any {
return JSON.parse(json)
}
// Good
function parseJSON(json: string): unknown {
return JSON.parse(json)
}
2. Prefer const
assertions
// Creates a more specific type
const colors = ['red', 'green', 'blue'] as const
type Color = typeof colors[number] // 'red' | 'green' | 'blue'
3. Use type predicates for runtime checks
function isString(value: unknown): value is string {
return typeof value === 'string'
}
// Now TypeScript knows the type after the check
if (isString(userInput)) {
// userInput is definitely a string here
console.log(userInput.toUpperCase())
}
Conclusion
TypeScript’s type system is incredibly powerful. These patterns and utilities help you write more expressive, safer code while maintaining the flexibility that makes JavaScript great.
The key is to start simple and gradually adopt more advanced patterns as your needs grow. Your future self (and your teammates) will thank you for the extra type safety!