TypeScript’s type system is one of the most powerful in mainstream programming languages. This note covers advanced type features that can make your code safer and more expressive.
Conditional Types
What Are Conditional Types?
Conditional types allow you to create types that depend on other types.
type IsString<T> = T extends string ? true : false;type A = IsString<"hello">; // truetype B = IsString<42>; // false
Real-World Use
type ApiResponse<T> = T extends "user" ? UserData : T extends "post" ? PostData : never;
Mapped Types
Transforming Types
Mapped types let you create new types by transforming existing ones.
// Make all properties optionaltype Partial<T> = { [P in keyof T]?: T[P];};// Make all properties requiredtype Required<T> = { [P in keyof T]-?: T[P];};// Make all properties readonlytype Readonly<T> = { readonly [P in keyof T]: T[P];};
Practical Example
interface User { id: number; name: string; email: string; age: number;}type UserUpdate = Partial<User>; // All fields optionaltype UserPreview = Pick<User, "id" | "name">; // Only id and nametype UserWithoutEmail = Omit<User, "email">; // All except email
Don't Overuse
Complex mapped types can be hard to read. Use them judiciously.
Template Literal Types
String Manipulation
TypeScript can manipulate string types at the type level.
type Shape = | { kind: "circle"; radius: number } | { kind: "rectangle"; width: number; height: number } | { kind: "triangle"; base: number; height: number };function area(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "rectangle": return shape.width * shape.height; case "triangle": return (shape.base * shape.height) / 2; }}
Exhaustiveness Checking
function assertNever(x: never): never { throw new Error("Unexpected value");}function area(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; // ... other cases default: return assertNever(shape); // Compile error if case missing }}
Utility Types
Built-in Helpers
TypeScript provides many utility types:
Type
Purpose
Example
Partial<T>
All optional
Partial<User>
Required<T>
All required
Required<Config>
Readonly<T>
All readonly
Readonly<State>
Pick<T, K>
Select fields
`Pick<User, “id"
Omit<T, K>
Exclude fields
Omit<User, "password">
Record<K, V>
Key-value map
Record<string, number>
Exclude<T, U>
Remove types
`Exclude<“a"
Extract<T, U>
Keep types
`Extract<“a"
NonNullable<T>
Remove null
`NonNullable<string
ReturnType<T>
Function return
ReturnType<typeof fn>
Parameters<T>
Function params
Parameters<typeof fn>
Infer Keyword
Type Inference
The infer keyword lets you extract types from other types.
// Extract array element typetype ElementOf<T> = T extends (infer E)[] ? E : never;type A = ElementOf<string[]>; // stringtype B = ElementOf<number[]>; // number// Extract function return typetype ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;// Extract promise typetype Unwrap<T> = T extends Promise<infer U> ? U : T;type C = Unwrap<Promise<string>>; // stringtype D = Unwrap<number>; // number
Branded Types
Type Safety for Primitives
Branded types prevent mixing up similar primitive types.
type UserId = string & { __brand: "UserId" };type PostId = string & { __brand: "PostId" };function createUserId(id: string): UserId { return id as UserId;}function getUser(id: UserId): User { // ...}const userId = createUserId("123");const postId = "456" as PostId;getUser(userId); // ✅ WorksgetUser(postId); // ❌ Compile error
Type Assertions
Avoid as assertions when possible. Branded types are safer.