Level up your TypeScript with generics, utility types, conditional types, mapped types, and template literal types — with practical examples.
Generics let you write reusable, type-safe code without sacrificing flexibility.
// Without generics — loses type information
function first(arr: any[]): any { return arr[0]; }
// With generics — type preserved
function first<T>(arr: T[]): T | undefined { return arr[0]; }
const num = first([1, 2, 3]); // type: number | undefined
const str = first(['a', 'b']); // type: string | undefined
// Generic with constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
getProperty({ name: 'Trong', age: 30 }, 'name'); // ✅
getProperty({ name: 'Trong', age: 30 }, 'email'); // ❌ compile error
TypeScript ships with powerful built-in utility types:
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
}
type PartialUser = Partial<User>; // all fields optional
type RequiredUser = Required<PartialUser>; // all fields required
type UserPreview = Pick<User, 'id' | 'name'>; // subset of fields
type NoEmail = Omit<User, 'email'>; // exclude fields
type ReadonlyUser = Readonly<User>; // all fields readonly
// Record: create a typed dictionary
type RolePermissions = Record<User['role'], string[]>;
const permissions: RolePermissions = {
admin: ['read', 'write', 'delete'],
user: ['read'],
};
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<string>; // false
// Infer: extract type from inside another type
type ElementType<T> = T extends (infer U)[] ? U : never;
type E = ElementType<string[]>; // string
// Unwrap a Promise
type Awaited<T> = T extends Promise<infer R> ? R : T;
type R = Awaited<Promise<number>>; // number
// Make all properties nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Make all properties optional and readonly
type DeepPartialReadonly<T> = {
readonly [K in keyof T]?: T[K] extends object ? DeepPartialReadonly<T[K]> : T[K];
};
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'
// Strongly-typed CSS property builder
type Direction = 'top' | 'right' | 'bottom' | 'left';
type MarginProp = `margin${Capitalize<Direction>}`;
// 'marginTop' | 'marginRight' | 'marginBottom' | 'marginLeft'
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function handleResult<T>(result: Result<T>) {
if (result.success) {
console.log(result.data); // TypeScript knows data exists here
} else {
console.error(result.error); // and error exists here
}
}