Partial and Required
Instead of redefining an interface just to mark everything optional for an edit form or patch API, wrap it in Partial<T>. Every field becomes optional in one go. Conversely, when you need to strictly validate that every optional configuration has been resolved, Required<T> strips away all question marks:
interface User {
id: string
name: string
email?: string
}
function updateUser(id: string, fields: Partial<User>) {
// fields can have any subset of User properties
}
type StrictUser = Required<User>
// StrictUser requires id, name AND email
Pick and Omit
When a component or handler only cares about a fraction of a large data model, don't copy-paste properties. Use Pick<T, Keys> to extract only the exact keys you need, or Omit<T, Keys> to drop sensitive or irrelevant fields like passwords and internal timestamps:
interface Product {
id: number
title: string
price: number
stock: number
secretSupplierId: string
}
type ProductPreview = Pick<Product, 'id' | 'title' | 'price'>
type PublicProduct = Omit<Product, 'secretSupplierId'>
Record
Stop typing generic objects as { [key: string]: any }. The Record<Keys, Type> utility creates clean, strictly-typed dictionary maps. Pair it with a union of string literals to guarantee that every single case is handled:
type Role = 'admin' | 'editor' | 'viewer'
interface Permission {
canEdit: boolean
canDelete: boolean
}
const rolePermissions: Record<Role, Permission> = {
admin: { canEdit: true, canDelete: true },
editor: { canEdit: true, canDelete: false },
viewer: { canEdit: false, canDelete: false }
}
ReturnType and Parameters
Writing repetitive types for complex API clients, factories, or third-party library functions is fragile. TypeScript can extract the return signature directly using ReturnType<typeof fn>, and function argument tuples with Parameters<typeof fn>:
function createSession(userId: string, rememberMe: boolean) {
return {
token: 'xyz-secret',
createdAt: new Date(),
userId
}
}
type Session = ReturnType<typeof createSession>
type SessionArgs = Parameters<typeof createSession> // [string, boolean]
Awaited
Working with asynchronous functions often results in types wrapped inside Promise<T>. The Awaited<T> utility recursively unwraps promises to grab the underlying resolved data type, matching the exact runtime behavior of await:
async function fetchAccount() {
return { id: 101, username: 'octocat', active: true }
}
type Account = Awaited<ReturnType<typeof fetchAccount>>
// Account is { id: number, username: string, active: boolean }
Satisfies Operator
When you annotate a variable with a type (like const config: Config = ...), TypeScript widens the properties and throws away specific literal information. The satisfies operator validates that your object matches the type contract while preserving the exact literal types, autocomplete, and method chaining:
type Palette = Record<string, string | [number, number, number]>
const theme = {
primary: '#3178C6',
secondary: [31, 120, 198]
} satisfies Palette
// Works! TypeScript remembers 'primary' is a string:
theme.primary.toLowerCase()
// And remembers 'secondary' is a tuple:
theme.secondary.map(val => val * 2)
Const Assertions
Adding as const to any array or object literal turns every property into a readonly literal type. Arrays become immutable fixed-length tuples, preventing accidental mutations and turning object values into strict union types instantly without writing an enum:
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE'] as const
type HttpMethod = typeof HTTP_METHODS[number]
// HttpMethod is 'GET' | 'POST' | 'PUT' | 'DELETE'
const routes = {
home: '/',
docs: '/docs'
} as const
// routes.home is strictly '/' rather than string
Discriminated Unions
Representing UI or network states with multiple independent booleans (like isLoading and isError) often creates impossible states. Discriminated unions use a shared literal property (like status) that lets TypeScript narrow the type automatically inside a switch or if block:
type NetworkState =
| { status: 'loading' }
| { status: 'success'; data: string[] }
| { status: 'error'; error: Error }
function render(state: NetworkState) {
switch (state.status) {
case 'loading':
return 'Loading spinner...'
case 'success':
return `Loaded: ${state.data.join(', ')}`
case 'error':
return `Failed: ${state.error.message}`
}
}
Template Literal Types
Similar to JavaScript template literals, TypeScript allows you to interpolate types within string literals. This makes it effortless to generate complex string unions, such as type-safe event handlers, BEM CSS classes, or route patterns:
type Entity = 'user' | 'order' | 'product'
type Action = 'create' | 'update' | 'delete'
type EventName = `${Entity}:${Action}`
// 'user:create' | 'user:update' | 'user:delete' | ...
type Direction = 'top' | 'right' | 'bottom' | 'left'
type MarginProperty = `margin-${Direction}`
// 'margin-top' | 'margin-right' | 'margin-bottom' | 'margin-left'