If you only use TypeScript to write interfaces and type aliases, you are tapping maybe 20% of its potential. Generics, utility types and conditional types let the compiler enforce complex business rules and catch runtime bugs at compile time. This article unlocks TypeScript's real power.

Generics

Defer the type of a function or type until call time. Code reuse + type safety, together.

// Simple generic
function identity<T>(value: T): T {
    return value;
}
identity<number>(42);         // T = number
identity('hello');            // T = string (inference)

// Generic constraint
function getLength<T extends { length: number }>(x: T): number {
    return x.length;
}
getLength('str');     // OK
getLength([1, 2, 3]); // OK
getLength(42);        // ERROR

// Multiple type params
function pair<K, V>(key: K, value: V): [K, V] {
    return [key, value];
}
const p = pair('age', 30);    // [string, number]

Built-in Utility Types

interface User {
    id: number;
    name: string;
    email: string;
    password: string;
    createdAt: Date;
}

// Partial — all fields optional
type UserUpdate = Partial<User>;
// { id?: number; name?: string; ... }

// Required — all fields required
type StrictUser = Required<UserUpdate>;

// Pick — select specific fields
type UserPublic = Pick<User, 'id' | 'name' | 'email'>;

// Omit — drop specific fields
type UserSafe = Omit<User, 'password'>;

// Readonly
type Frozen = Readonly<User>;

// Record — key-value type
type UsersMap = Record<string, User>;
const users: UsersMap = { 'u1': { ... }, 'u2': { ... } };

ReturnType, Parameters, Awaited

function createUser(data: { name: string; email: string }): Promise<User> { ... }

type CreateUserParams = Parameters<typeof createUser>[0];
// { name: string; email: string }

type CreateUserResult = ReturnType<typeof createUser>;
// Promise<User>

type CreateUserResolved = Awaited<CreateUserResult>;
// User

// Auto-derive the API response type
type ApiUser = Awaited<ReturnType<typeof createUser>>;

Conditional Types

// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>;  // true
type B = IsString<42>;       // false

// Distributive conditional types (distribute over unions)
type NonNull<T> = T extends null | undefined ? never : T;
type Result = NonNull<string | null | number>;  // string | number

// Real world: filter out nullable fields
type NonNullableKeys<T> = {
    [K in keyof T]-?: T[K] extends null | undefined ? never : K
}[keyof T];

The infer Keyword

// Extract array element type
type ElementType<T> = T extends (infer U)[] ? U : never;
type A = ElementType<string[]>;     // string
type B = ElementType<number[][]>;   // number[]

// Extract function return type
type MyReturn<T> = T extends (...args: any[]) => infer R ? R : never;

// Promise unwrap
type Unwrap<T> = T extends Promise<infer U> ? U : T;

// Extract from a template literal
type GetFirstWord<S> = S extends `${infer First} ${string}` ? First : S;
type W = GetFirstWord<'Hello World'>;  // 'Hello'

Mapped Types

// Transform an entire interface
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface User { name: string; age: number; }
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }

// Make everything nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };

// Deep partial
type DeepPartial<T> = {
    [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

Template Literal Types

type EventName = `on${Capitalize<'click' | 'change' | 'hover'>}`;
// 'onClick' | 'onChange' | 'onHover'

type Endpoint = `/${'api' | 'admin'}/${'users' | 'posts'}/:id`;
// '/api/users/:id' | '/api/posts/:id' | '/admin/users/:id' | '/admin/posts/:id'

// HTTP method + path narrow type
type Route =
    | `GET /${string}`
    | `POST /${string}`
    | `DELETE /${string}`;
const r: Route = 'GET /users';   // OK
const w: Route = 'PATCH /users'; // ERROR

Discriminated Unions

type ApiResponse<T> =
    | { status: 'success'; data: T }
    | { status: 'error'; error: string }
    | { status: 'loading' };

function render<T>(res: ApiResponse<T>) {
    switch (res.status) {
        case 'success': return res.data;       // T
        case 'error':   return res.error;      // string
        case 'loading': return 'Loading...';
    }
}
// TypeScript does an exhaustiveness check — it warns if you skip a case

Type Guards

// Custom type guard
function isUser(x: unknown): x is User {
    return typeof x === 'object' && x !== null
        && 'id' in x && typeof x.id === 'number'
        && 'email' in x;
}

// Usage
const data: unknown = fetchData();
if (isUser(data)) {
    data.email;  // TS now knows it's a User
}

// Zod for runtime + type (industry standard)
import { z } from 'zod';
const UserSchema = z.object({
    id: z.number(),
    email: z.string().email(),
    age: z.number().min(0).max(150)
});
type User = z.infer<typeof UserSchema>;  // type is generated automatically

The satisfies Operator

// 4.9+ satisfies: check type compatibility without losing literal types
const config = {
    debug: false,
    port: 3000,
    env: 'production'
} satisfies { debug: boolean; port: number; env: 'development' | 'production' };

config.env;  // 'production' (literal) — with 'as' it would widen to string

Practical: Type-Safe Event Emitter

type EventMap = {
    'user:created': { id: string; email: string };
    'user:deleted': { id: string };
    'error': Error;
};

class TypedEmitter<T> {
    on<K extends keyof T>(event: K, listener: (data: T[K]) => void) { ... }
    emit<K extends keyof T>(event: K, data: T[K]) { ... }
}

const bus = new TypedEmitter<EventMap>();
bus.on('user:created', data => data.email);  // OK, data: { id, email }
bus.emit('user:deleted', { id: '42' });      // OK
bus.emit('user:deleted', { email: 'x' });    // ERROR — id is missing

Conclusion

Advanced TypeScript isn't about learning every detail of the language, it's about encoding your own domain as types. Libraries like Zod unite runtime and type. The combo of discriminated unions + type guards + satisfies is the backbone of most modern TS codebases.

TypeScript training and consulting

Reach out to KEYDAL for type-safe architecture, Zod integration and TS code review. Contact us

WhatsApp