| name | typescript-advanced-types |
| description | Advanced TypeScript types (generics, conditionals, mapped, utilities). Use when building type-safe libraries or implementing complex type logic. |
| version | 1.0.0 |
TypeScript Advanced Types
Overview
Comprehensive guidance for mastering TypeScript's advanced type system including generics, conditional types, mapped types, template literal types, and utility types for building robust, type-safe applications.
When to Use This Skill
- Building type-safe libraries or frameworks
- Creating reusable generic components
- Implementing complex type inference logic
- Designing type-safe API clients
- Building form validation systems
- Creating strongly-typed configuration objects
- Implementing type-safe state management
- Migrating JavaScript codebases to TypeScript
Core Concepts
1. Generics
Purpose: Create reusable, type-flexible components while maintaining type safety.
Basic Generic Function:
function identity<T>(value: T): T {
return value;
}
const num = identity<number>(42);
const str = identity<string>("hello");
const auto = identity(true);
Generic Constraints:
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length);
return item;
}
logLength("hello");
logLength([1, 2, 3]);
logLength({ length: 10 });
Multiple Type Parameters:
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const merged = merge(
{ name: "John" },
{ age: 30 }
);
2. Conditional Types
Purpose: Create types that depend on conditions, enabling sophisticated type logic.
Basic Conditional Type:
type IsString<T> = T extends string ? true : false;
type A = IsString<string>;
type B = IsString<number>;
Extracting Return Types:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: "John" };
}
type User = ReturnType<typeof getUser>;
Distributive Conditional Types:
type ToArray<T> = T extends any ? T[] : never;
type StrOrNumArray = ToArray<string | number>;
Nested Conditions:
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T1 = TypeName<string>;
type T2 = TypeName<() => void>;
3. Mapped Types
Purpose: Transform existing types by iterating over their properties.
Basic Mapped Type:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
id: number;
name: string;
}
type ReadonlyUser = Readonly<User>;
Optional Properties:
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialUser = Partial<User>;
Key Remapping:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
Filtering Properties:
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
};
interface Mixed {
id: number;
name: string;
age: number;
active: boolean;
}
type OnlyNumbers = PickByType<Mixed, number>;
4. Template Literal Types
Purpose: Create string-based types with pattern matching and transformation.
Basic Template Literal:
type EventName = "click" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventName>}`;
String Manipulation:
type UppercaseGreeting = Uppercase<"hello">;
type LowercaseGreeting = Lowercase<"HELLO">;
type CapitalizedName = Capitalize<"john">;
type UncapitalizedName = Uncapitalize<"John">;
Path Building:
type Path<T> = T extends object
? { [K in keyof T]: K extends string
? `${K}` | `${K}.${Path<T[K]>}`
: never
}[keyof T]
: never;
interface Config {
server: {
host: string;
port: number;
};
database: {
url: string;
};
}
type ConfigPath = Path<Config>;
5. Utility Types
Built-in Utility Types:
type PartialUser = Partial<User>;
type RequiredUser = Required<PartialUser>;
type ReadonlyUser = Readonly<User>;
type UserName = Pick<User, "name" | "email">;
type UserWithoutPassword = Omit<User, "password">;
type T1 = Exclude<"a" | "b" | "c", "a">;
type T2 = Extract<"a" | "b" | "c", "a" | "b">;
type T3 = NonNullable<string | null | undefined>;
type PageInfo = Record<"home" | "about", { title: string }>;
Advanced Patterns
Pattern 1: Type-Safe Event Emitter
type EventMap = {
"user:created": { id: string; name: string };
"user:updated": { id: string };
"user:deleted": { id: string };
};
class TypedEventEmitter<T extends Record<string, any>> {
private listeners: {
[K in keyof T]?: Array<(data: T[K]) => void>;
} = {};
on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]!.push(callback);
}
emit<K extends keyof T>(event: K, data: T[K]): void {
const callbacks = this.listeners[event];
if (callbacks) {
callbacks.forEach(callback => callback(data));
}
}
}
const emitter = new TypedEventEmitter<EventMap>();
emitter.on("user:created", (data) => {
console.log(data.id, data.name);
});
emitter.emit("user:created", { id: "1", name: "John" });
Pattern 2: Deep Readonly/Partial
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: DeepReadonly<T[P]>
: T[P];
};
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: DeepPartial<T[P]>
: T[P];
};
interface Config {
server: {
host: string;
port: number;
ssl: {
enabled: boolean;
cert: string;
};
};
}
type ReadonlyConfig = DeepReadonly<Config>;
type PartialConfig = DeepPartial<Config>;
Pattern 3: Discriminated Unions
type Success<T> = {
status: "success";
data: T;
};
type Error = {
status: "error";
error: string;
};
type Loading = {
status: "loading";
};
type AsyncState<T> = Success<T> | Error | Loading;
function handleState<T>(state: AsyncState<T>): void {
switch (state.status) {
case "success":
console.log(state.data);
break;
case "error":
console.log(state.error);
break;
case "loading":
console.log("Loading...");
break;
}
}
Type Inference Techniques
1. Infer Keyword
type ElementType<T> = T extends (infer U)[] ? U : never;
type NumArray = number[];
type Num = ElementType<NumArray>;
type PromiseType<T> = T extends Promise<infer U> ? U : never;
type AsyncNum = PromiseType<Promise<number>>;
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function foo(a: string, b: number) {}
type FooParams = Parameters<typeof foo>;
2. Type Guards
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isArrayOf<T>(
value: unknown,
guard: (item: unknown) => item is T
): value is T[] {
return Array.isArray(value) && value.every(guard);
}
const data: unknown = ["a", "b", "c"];
if (isArrayOf(data, isString)) {
data.forEach(s => s.toUpperCase());
}
3. Assertion Functions
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Not a string");
}
}
function processValue(value: unknown) {
assertIsString(value);
console.log(value.toUpperCase());
}
Quick Reference
| Utility | Purpose |
|---|
Partial<T> | All properties optional |
Required<T> | All properties required |
Readonly<T> | All properties readonly |
Pick<T, K> | Select properties |
Omit<T, K> | Remove properties |
Exclude<T, U> | Remove from union |
Extract<T, U> | Keep from union |
NonNullable<T> | Remove null/undefined |
Record<K, T> | Object with keys K, values T |
ReturnType<T> | Function return type |
Parameters<T> | Function parameter types |
Best Practices
Do
- Use
unknown over any - enforce type checking
- Prefer
interface for object shapes - better error messages
- Use
type for unions and complex types
- Leverage type inference - let TypeScript infer when possible
- Create helper types - build reusable utilities
- Use const assertions - preserve literal types
- Use type guards instead of assertions
- Enable strict mode in tsconfig
Don't
- Over-use
any - defeats TypeScript's purpose
- Ignore strict null checks - leads to runtime errors
- Create overly complex types - slows compilation
- Forget readonly modifiers - allows unintended mutations
- Use type assertions when guards work - loses type safety
Common Pitfalls
| Pitfall | Solution |
|---|
| Circular type references | Break cycle with explicit types |
| Deeply nested conditionals | Simplify or use helper types |
| Missing discriminant | Add type or kind field |
Forgetting infer scope | Use inside extends clause |
| Union distribution unexpected | Wrap in tuple [T] |
Resources