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'