Tag: javascript

  • TypeScript Tips & Tricks: Patterns That Separate Juniors from Seniors

    TypeScript has become the default choice for serious JavaScript development. With over 43% of developers using it (Stack Overflow 2025), its type system catches entire categories of bugs at compile time. But TypeScript’s power goes far beyond basic annotations — its advanced type system is a programming language in its own right. Here are techniques that separate proficient developers from beginners.

    Generics: Write Once, Type Everything

    // Generic API response wrapper — type-safe for any data shape
    interface ApiResponse<T> {
        status: number;
        data: T;
        timestamp: string;
    }
    
    function handleResponse<T>(response: ApiResponse<T>): T {
        if (response.status >= 400) throw new Error(`API error: ${response.status}`);
        return response.data;
    }
    
    interface User { id: string; name: string; email: string; }
    const userResp: ApiResponse<User> = { status: 200, data: { id: '1', name: 'Alice', email: 'alice@example.com' }, timestamp: new Date().toISOString() };
    const user = handleResponse(userResp);
    // user is typed as User — full autocomplete, full safety
    
    // Constrained generic — T must have an 'id' property
    function findById<T extends { id: string }>(items: T[], id: string): T | undefined {
        return items.find(item => item.id === id);
    }

    Discriminated Unions

    By adding a literal type field, you get exhaustive pattern matching that eliminates impossible states:

    type FetchState<T> =
        | { type: 'idle' }
        | { type: 'loading'; startedAt: number }
        | { type: 'success'; data: T; fetchedAt: number }
        | { type: 'error'; message: string; retryCount: number };
    
    function renderState<T>(state: FetchState<T>): string {
        switch (state.type) {
            case 'idle':    return 'Ready';
            case 'loading': return `Loading... (${Date.now() - state.startedAt}ms)`;
            case 'success': return `Got: ${JSON.stringify(state.data)}`;
            case 'error':   return `Error: ${state.message} (retry ${state.retryCount}/3)`;
            // Remove a case → compile error. Impossible to forget a state.
        }
    }

    Utility Types You Should Know

    interface User { id: string; name: string; email: string; role: 'admin'|'editor'|'viewer'; createdAt: Date; }
    
    type UserUpdate = Partial<Omit<User, 'id' | 'createdAt'>>;  // All fields optional except id/createdAt
    type UserSummary = Pick<User, 'id' | 'name' | 'role'>;       // Just id, name, role
    type UserMap = Record<string, User>;                          // Typed lookup table
    type NotAdmin = Exclude<User['role'], 'admin'>;               // 'editor' | 'viewer'
    
    // ReturnType extracts a function's return type
    function createUser(name: string) { return { id: crypto.randomUUID(), name, createdAt: new Date() }; }
    type CreatedUser = ReturnType<typeof createUser>;

    Template Literal Types

    type Entity = 'user' | 'order' | 'product';
    type Action = 'created' | 'updated' | 'deleted';
    type EventName = `${Entity}:${Action}`;
    // 'user:created' | 'user:updated' | ... (9 combinations, all type-safe)

    The satisfies Operator

    Validates that a value matches a type WITHOUT widening its inferred type — best of both worlds:

    type Theme = Record<string, string | number>;
    const theme = {
        primary: '#6366f1',
        fontSize: 16,
    } satisfies Theme;
    theme.primary;   // Type: '#6366f1' (not string!)
    theme.fontSize;  // Type: 16 (not number!)

    Mapped & Conditional Types

    // Make all string properties nullable
    type Nullable<T> = { [K in keyof T]: T[K] extends string ? T[K] | null : T[K]; };
    
    // Deep readonly — recursively freeze nested objects
    type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]; };

    These patterns compound. Once you internalize generics, discriminated unions, utility types, and satisfies, you write code that’s simultaneously more flexible and more type-safe.

    Further reading: TypeScript Handbook | SO 2025 Survey