TypeScript makes JavaScript code bases robust, but many developers only use basic types, interfaces, and union operations. When writing dynamic helper utilities or complex libraries, standard type interfaces can fall short. TypeScript's advanced type systems—specifically conditional types and mapped types—allow you to write reusable types that transform and adapt dynamically.
Conditional types let you express logic in type definitions using a ternary syntax that mirrors JavaScript operators:
SomeType extends OtherType ? TrueType : FalseType
Let's look at a practical utility type that extracts the payload type from a promise wrapper:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type FetchResult = Promise<{ success: boolean; data: string[] }>;
// ResolvedResult is resolved to { success: boolean; data: string[] }
type ResolvedResult = UnwrapPromise<FetchResult>;
Using the infer keyword lets TypeScript inspect type structures and extract matching inner signatures dynamically during compile analysis.
Mapped types iterate over keys of an object schema to create new property types. They are defined using the in keyof mapping syntax:
type Optional<T> = {
[P in keyof T]?: T[P];
};
Let's build a mapped type utility that takes a type interface and makes all its values read-only, while stripping out nullable options:
type SecureType<T> = {
readonly [K in keyof T]-?: NonNullable<T[K]>;
};
interface UserConfig {
theme?: string | null;
cacheLimit?: number;
apiEndpoint?: string;
}
// UserConfigSecure values are read-only and cannot be null or undefined
type UserConfigSecure = SecureType<UserConfig>;
By merging conditional operations inside mapped keys, you can filter object schemas based on property criteria (such as selecting only functions):
type FunctionKeys<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
interface UserService {
dbUrl: string;
connect(): Promise<void>;
fetchUsers(limit: number): string[];
}
// ServiceMethods resolves to: "connect" | "fetchUsers"
type ServiceMethods = FunctionKeys<UserService>;
Unlocking TypeScript's advanced type features allows developers to write robust, dynamic APIs and libraries. Utilizing mapped types, conditional assertions, and type inference reduces code duplication while catching potential logical errors at compile time.