TypeScript has become the industry standard for large-scale web development. While most developers know the basics of interfaces and types, the real power lies in its advanced type system. Here are 5 patterns that will distinguish you as a senior engineer.
1. Generic Constraints
Generics are powerful, but sometimes you need to limit what can be passed in. extends is your friend here.
interface HasId { id: string; } function getById<T extends HasId>(list: T[], id: string): T | undefined { return list.find((item) => item.id === id); }
By ensuring T extends HasId, we guarantee that accessing .id inside the function is safe.
2. Conditional Types
Conditional types allow you to create non-uniform type mappings. The syntax is similar to the ternary operator in JavaScript.
type IsString<T> = T extends string ? true : false; type A = IsString<string>; // true type B = IsString<number>; // false
A practical use case is filtering out types from a union:
type Diff<T, U> = T extends U ? never : T; type NonNullable<T> = Diff<T, null | undefined>;
3. Mapped Types
Mapped types allow you to create new types based on old ones by transforming properties.
type ReadOnly<T> = { readonly [P in keyof T]: T[P]; }; interface User { name: string; age: number; } type ReadOnlyUser = ReadOnly<User>;
You can even add or remove modifiers:
type Mutable<T> = { -readonly [P in keyof T]: T[P]; };
4. Template Literal Types
Introduced in TypeScript 4.1, these allow you to manipulate string types directly.
type World = 'world'; type Greeting = `hello ${World}`; // "hello world" type Color = 'red' | 'blue'; type Quantity = 'light' | 'dark'; type Palette = `${Quantity}-${Color}`; // "light-red" | "light-blue" | "dark-red" | "dark-blue"
This is incredibly useful for typing strings that follow a specific pattern, like CSS classes or event names.
5. The infer Keyword
The infer keyword within conditional types allows you to extract types from other types.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any; function check(): boolean { return true; } type CheckReturn = ReturnType<typeof check>; // boolean
Here, we are asking TypeScript to "infer" the return type R of a function and return it.
Conclusion
Mastering these patterns allows you to write libraries and utilities that are robust and provide excellent developer experience (DX). The goal of advanced TypeScript is not complexity for complexity's sake, but safety and expressiveness.