Module 9: Advanced Types
This module covers advanced type manipulation techniques including mapped types, conditional types, and index types.
1. Index Types
keyof Operator
Extract all keys from a type as a union.
interface Person {
name: string;
age: number;
email: string;
}
type PersonKeys = keyof Person; // "name" | "age" | "email"
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let person: Person = { name: "Alice", age: 25, email: "alice@example.com" };
let name = getProperty(person, "name"); // string
let age = getProperty(person, "age"); // number
Indexed Access Types
type Person = {
name: string;
age: number;
address: {
street: string;
city: string;
};
};
type Name = Person["name"]; // string
type Age = Person["age"]; // number
type Address = Person["address"]; // { street: string; city: string; }
type City = Person["address"]["city"]; // string
2. Mapped Types
Transform properties of a type.
Basic Mapped Type
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};
// Equivalent to:
type ReadonlyPerson2 = {
readonly name: string;
readonly age: number;
readonly email: string;
};
Optional Mapped Type
type PartialPerson = {
[K in keyof Person]?: Person[K];
};
// All properties become optional
let partialPerson: PartialPerson = {
name: "Alice"
// age and email are optional
};
Creating Reusable Mapped Types
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
type MyRequired<T> = {
[K in keyof T]-?: T[K]; // Remove optionality
};
3. Conditional Types
Types that depend on conditions.
Basic Conditional Type
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
Conditional Type with Union
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 T0 = TypeName<string>; // "string"
type T1 = TypeName<number>; // "number"
type T2 = TypeName<() => void>; // "function"
4. infer Keyword
Extract types from other types within conditional types.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { name: "Alice", age: 25 };
}
type User = ReturnType<typeof getUser>; // { name: string; age: number; }
Extract Function Parameters
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function createUser(name: string, age: number) {
return { name, age };
}
type CreateUserParams = Parameters<typeof createUser>; // [string, number]
Extract Array Element Type
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Numbers = ArrayElement<number[]>; // number
type Strings = ArrayElement<string[]>; // string
5. Template Literal Types
Create types using template literal syntax.
type Greeting = `Hello ${string}`;
let greeting1: Greeting = "Hello World"; // ✅ OK
let greeting2: Greeting = "Hello Alice"; // ✅ OK
// let greeting3: Greeting = "Hi there"; // ❌ Error
Combining String Literals
type Color = "red" | "green" | "blue";
type Size = "small" | "medium" | "large";
type ColoredSize = `${Color}-${Size}`;
// "red-small" | "red-medium" | "red-large" |
// "green-small" | "green-medium" | "green-large" |
// "blue-small" | "blue-medium" | "blue-large"
String Manipulation Types
type Uppercase<S extends string> = intrinsic;
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;
type Loud = Uppercase<"hello">; // "HELLO"
type Quiet = Lowercase<"HELLO">; // "hello"
type Proper = Capitalize<"hello">; // "Hello"
type Informal = Uncapitalize<"Hello">; // "hello"
6. Utility Types (Built-in)
Partial<T>
Makes all properties optional.
interface User {
name: string;
age: number;
email: string;
}
type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string; }
function updateUser(user: User, updates: Partial<User>): User {
return { ...user, ...updates };
}
Required<T>
Makes all properties required.
interface Config {
host?: string;
port?: number;
}
type RequiredConfig = Required<Config>;
// { host: string; port: number; }
Readonly<T>
Makes all properties readonly.
type ReadonlyUser = Readonly<User>;
let user: ReadonlyUser = { name: "Alice", age: 25, email: "alice@example.com" };
// user.name = "Bob"; // ❌ Error: Cannot assign to 'name'
Pick<T, K>
Select specific properties.
type UserPreview = Pick<User, "name" | "email">;
// { name: string; email: string; }
Omit<T, K>
Exclude specific properties.
type UserWithoutEmail = Omit<User, "email">;
// { name: string; age: number; }
Record<K, T>
Create an object type with specific keys and value type.
type Roles = "admin" | "user" | "guest";
type RolePermissions = Record<Roles, string[]>;
let permissions: RolePermissions = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"]
};
Exclude<T, U>
Remove types from a union.
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<string | number | boolean, boolean>; // string | number
Extract<T, U>
Extract types from a union.
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | boolean, boolean>; // boolean
NonNullable<T>
Remove null and undefined.
type T0 = NonNullable<string | number | null | undefined>; // string | number
7. Recursive Types
Types that reference themselves.
interface TreeNode<T> {
value: T;
children?: TreeNode<T>[];
}
let tree: TreeNode<number> = {
value: 1,
children: [
{ value: 2 },
{
value: 3,
children: [
{ value: 4 },
{ value: 5 }
]
}
]
};
JSON Type
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
let data: JSONValue = {
name: "Alice",
age: 25,
hobbies: ["reading", "coding"],
address: {
city: "New York"
}
};
8. Type Branding
Create distinct types from the same underlying type.
type Brand<K, T> = K & { __brand: T };
type USD = Brand<number, "USD">;
type EUR = Brand<number, "EUR">;
function createUSD(amount: number): USD {
return amount as USD;
}
function createEUR(amount: number): EUR {
return amount as EUR;
}
let dollars: USD = createUSD(100);
let euros: EUR = createEUR(100);
// dollars = euros; // ❌ Error: Type 'EUR' is not assignable to type 'USD'
9. Advanced Conditional Type Patterns
Flatten Type
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
Awaited Type
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type Response = Awaited<Promise<string>>; // string
type NestedResponse = Awaited<Promise<Promise<number>>>; // number
10. Real-World Example: API Client
interface APIEndpoint {
"/users": {
GET: { response: User[] };
POST: { body: Omit<User, "id">; response: User };
};
"/users/:id": {
GET: { response: User };
PUT: { body: Partial<User>; response: User };
DELETE: { response: void };
};
}
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type EndpointConfig<
Path extends keyof APIEndpoint,
Method extends keyof APIEndpoint[Path]
> = APIEndpoint[Path][Method];
function apiCall<
Path extends keyof APIEndpoint,
Method extends keyof APIEndpoint[Path] & HTTPMethod
>(
path: Path,
method: Method,
config?: EndpointConfig<Path, Method>
): Promise<EndpointConfig<Path, Method> extends { response: infer R } ? R : never> {
// Implementation
return null as any;
}
// Usage with full type safety
apiCall("/users", "GET"); // Promise<User[]>
apiCall("/users", "POST", { body: { name: "Alice", age: 25, email: "alice@example.com" } }); // Promise<User>
Key Takeaways
✅ keyof extracts object keys as unions
✅ Mapped types transform existing types
✅ Conditional types create type logic
✅ infer extracts types in conditionals
✅ Template literals create string-based types
✅ Utility types provide common transformations
✅ Recursive types handle nested structures
Practice Exercises
Exercise 1: DeepReadonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
Exercise 2: Function Property Names
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
interface Example {
name: string;
age: number;
greet(): void;
sayBye(): void;
}
type FuncProps = FunctionPropertyNames<Example>; // "greet" | "sayBye"
Exercise 3: Promise Unwrap
Create a type that unwraps nested promises.
Next Steps
In Module 10, we'll explore Modules and Namespaces to organize and structure large TypeScript applications.