| name | typescript-utility-types |
| user-invocable | false |
| description | Use when typeScript utility types, mapped types, and advanced type manipulation. Use when creating flexible, type-safe TypeScript code. |
| allowed-tools | ["Bash","Read","Write","Edit"] |
TypeScript Utility Types
Master TypeScript's powerful type system including built-in utility types,
mapped types, conditional types, and advanced type manipulation techniques
for creating flexible, type-safe code.
Built-in Utility Types
Partial and Required
interface User {
id: string;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>;
function updateUser(id: string, updates: Partial<User>): User {
const existingUser = getUser(id);
return { ...existingUser, ...updates };
}
updateUser('123', { name: 'John' });
updateUser('123', { age: 30 });
interface OptionalConfig {
host?: string;
port?: number;
timeout?: number;
}
type RequiredConfig = Required<OptionalConfig>;
function validateConfig(config: Required<OptionalConfig>): boolean {
return config.host.length > 0 && config.port > 0;
}
Pick and Omit
interface Article {
id: string;
title: string;
content: string;
author: string;
createdAt: Date;
updatedAt: Date;
views: number;
}
type ArticlePreview = Pick<Article, 'id' | 'title' | 'author'>;
function displayPreview(article: ArticlePreview): void {
console.log(`${article.title} by ${article.author}`);
}
type ArticleWithoutDates = Omit<Article, 'createdAt' | 'updatedAt'>;
type ArticleMetadata = Pick<Article, 'id' | 'author' | >;
= <, | | >;
Readonly and Record
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {
id: '1',
name: 'John',
email: 'john@example.com',
age: 30,
};
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
type UserRole = 'admin' | 'editor' | 'viewer';
type RolePermissions = Record<UserRole, string[]>;
const permissions: RolePermissions = {
admin: ['read', 'write', 'delete'],
editor: ['read', 'write'],
viewer: ['read'],
};
= | | | ;
= ;
= <, >;
Extract and Exclude
type Status = 'pending' | 'approved' | 'rejected' | 'cancelled';
type CompletedStatus = Extract<Status, 'approved' | 'rejected'>;
type ActiveStatus = Exclude<Status, 'approved' | 'rejected' | 'cancelled'>;
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'rectangle'; width: number; height: number };
type CircularShape = Extract<Shape, { kind: 'circle' }>;
type NonCircularShape = Exclude<Shape, { : }>;
ReturnType and Parameters
function createUser(name: string, age: number): User {
return {
id: generateId(),
name,
age,
email: `${name.toLowerCase()}@example.com`,
};
}
type UserFromFunction = ReturnType<typeof createUser>;
type CreateUserParams = Parameters<typeof createUser>;
function processData<T>(data: T[]): { count: number; items: T[] } {
return { count: data.length, items: data };
}
type ProcessResult = ReturnType<typeof processData<User>>;
Mapped Types
Basic Mapped Types
type Flags<T> = {
[P in keyof T]: boolean;
};
type UserFlags = Flags<User>;
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
type NullableUser = Nullable<User>;
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
type UserGetters = Getters<User>;
Mapped Type Modifiers
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}
type MutablePerson = Mutable<ReadonlyPerson>;
type Concrete<T> = {
[P in keyof T]-?: T[P];
};
interface OptionalUser {
name?: string;
age?: number;
}
type ConcreteUser = Concrete<OptionalUser>;
type Optional<T> = {
[P in keyof T]+?: T[P];
};
Advanced Mapped Types
type Promisify<T> = {
[P in keyof T]: Promise<T[P]>;
};
type AsyncUser = Promisify<User>;
type Boxed<T> = {
[P in keyof T]: { value: T[P] };
};
type BoxedUser = Boxed<User>;
type Proxy<T> = {
get(): T;
set(value: T): void;
};
type ProxiedProperties<T> = {
[P in keyof T]: Proxy<T[P]>;
};
Conditional Types
Basic Conditional Types
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>;
type Test2 = IsString<number>;
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>;
type T1 = TypeName<number>;
type T2 = TypeName<() => void>;
Distributive Conditional Types
type ToArray<T> = T extends any ? T[] : never;
type StrOrNumArray = ToArray<string | number>;
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type StrOrNumArrayNonDist = ToArrayNonDist<string | number>;
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;
Inferring Types with infer
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function example(): { x: number } {
return { x: 42 };
}
type ExampleReturn = GetReturnType<typeof example>;
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>;
type Num = Flatten<number>;
type Awaited<T> = T extends Promise<infer U> ? U : T;
type PromiseString = Awaited<Promise<string>>;
type RegularString = Awaited<string>;
<T> = T (: infer F, ...: []) =>
? F
: ;
(): {}
= < multi>;
Template Literal Types
Basic Template Literals
type World = 'world';
type Greeting = `hello ${World}`;
type Color = 'red' | 'blue' | 'green';
type Quantity = 'one' | 'two';
type ColoredQuantity = `${Quantity} ${Color}`;
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
String Manipulation Types
type UppercaseGreeting = Uppercase<'hello'>;
type LowercaseGreeting = Lowercase<'HELLO'>;
type CapitalizedGreeting = Capitalize<'hello'>;
type UncapitalizedGreeting = Uncapitalize<'Hello'>;
type GetterName<T extends string> = `get${Capitalize<T>}`;
type SetterName<T extends string> = `set${Capitalize<T>}`;
type UserNameGetter = GetterName<'name'>;
type UserNameSetter = SetterName<'name'>;
type Accessors<T> = {
[K in keyof T as GetterName<string & K>]: T[K];
} & {
[K keyof T < & K>]: ;
};
= <>;
Pattern Matching with Template Literals
type ExtractRouteParams<T extends string> =
T extends `${infer Start}/:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string }
: T extends `${infer Start}/:${infer Param}`
? { [K in Param]: string }
: {};
type Route1 = ExtractRouteParams<'/users/:userId/posts/:postId'>;
type Route2 = ExtractRouteParams<'/posts/:id'>;
type CSSProperty =
| 'color'
| 'background-color'
| 'font-size'
| 'margin-top';
type CamelCase<S extends string> = S extends `${infer P1}-${infer P2}${infer P3}`
? `${P1}${Uppercase<P2>}`
: S;
= <>;
Key Remapping
Remapping Keys in Mapped Types
type OmitByType<T, U> = {
[P in keyof T as T[P] extends U ? never : P]: T[P];
};
interface Mixed {
name: string;
age: number;
isActive: boolean;
count: number;
}
type OnlyStrings = OmitByType<Mixed, number | boolean>;
type Prefix<T, P extends string> = {
[K in keyof T as `${P}${string & K}`]: T[K];
};
type PrefixedUser = Prefix<User, 'user_'>;
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = <>;
Conditional Key Remapping
type PickByType<T, U> = {
[P in keyof T as T[P] extends U ? P : never]: T[P];
};
type NumberProperties = PickByType<Mixed, number>;
type RenameByType<T> = {
[K in keyof T as T[K] extends string
? `str_${string & K}`
: T[K] extends number
? `num_${string & K}`
: K]: T[K];
};
type RenamedMixed = RenameByType<Mixed>;
Advanced Type Manipulation
Recursive Types
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
const json: JSONValue = {
name: 'John',
age: 30,
hobbies: ['reading', 'coding'],
address: {
city: 'New York',
coordinates: [40.7128, -74.006],
},
};
type Path<T> = T extends object
? {
[K in keyof T]: K extends string
? T[K] extends object
? K | `${K}.${Path<T[K]>}`
: K
: never;
}[keyof T]
: never;
type UserPath = Path<User>;
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends ? <T[P]> : T[P];
};
Union and Intersection Utilities
type UnionToIntersection<U> = (
U extends any ? (x: U) => void : never
) extends (x: infer I) => void
? I
: never;
type Union = { a: string } | { b: number };
type Intersection = UnionToIntersection<Union>;
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
interface PartialRequired {
required: string;
optional?: number;
}
type Required = RequiredKeys<PartialRequired>;
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
type = <>;
Function Type Utilities
type Asyncify<T extends (...args: any[]) => any> = (
...args: Parameters<T>
) => Promise<ReturnType<T>>;
function syncFunction(x: number): string {
return x.toString();
}
type AsyncFunction = Asyncify<typeof syncFunction>;
type Curry<T> = T extends (
arg: infer A,
...args: infer R
) => infer Return
? (arg: A) => R extends []
? Return
: Curry<(...args: R) => Return>
: never;
type CurriedFunction = Curry<(a: string, b: number, c: ) => >;
Builder Pattern Types
type Builder<T, R = {}> = {
[K in keyof T]: (
value: T[K]
) => Builder<Omit<T, K>, R & Pick<T, K>>;
} & (R extends T ? { build(): T } : {});
interface Config {
host: string;
port: number;
ssl: boolean;
}
function createBuilder<T>(): Builder<T> {
const values: Partial<T> = {};
const builder = new Proxy(
{},
{
get(_, prop) {
if (prop === 'build') {
return () => values as T;
}
return (value: any) => {
values[prop as keyof T] = value;
return builder;
};
},
}
) as Builder<T>;
return builder;
}
const config = createBuilder<Config>()
.host('localhost')
.()
.()
.();
Type Inference Helpers
Const Assertions
const colors1 = ['red', 'blue', 'green'];
type Colors1 = typeof colors1;
const colors2 = ['red', 'blue', 'green'] as const;
type Colors2 = typeof colors2;
type Color = Colors2[number];
const config = {
api: {
url: 'https://api.example.com',
timeout: 5000,
},
} as const;
type ConfigUrl = typeof config.api.url;
Type Guards with User-Defined Type Guards
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
'email' in value
);
}
function hasProperty<K extends string>(
key: K
): <T>(obj: T) => obj is T & Record<K, unknown> {
return (obj): obj is T & Record<K, unknown> => {
return typeof obj === 'object' && obj !== null && key in obj;
};
}
const hasName = hasProperty('name');
if (hasName(someObject)) {
console.log(someObject.name);
}
Best Practices
-
Prefer Built-in Utility Types: Use TypeScript's built-in utility
types (Partial, Pick, Omit, etc.) before creating custom ones for better
readability.
-
Use Const Assertions: Apply const assertions to arrays and objects
when you need literal types instead of widened types.
-
Keep Types Simple: Avoid overly complex type transformations. If a
type becomes hard to understand, consider refactoring or using multiple
simpler types.
-
Document Complex Types: Add comments to explain non-obvious type
transformations, especially for mapped and conditional types.
-
Leverage Type Inference: Let TypeScript infer types when possible
rather than explicitly declaring them everywhere.
-
Use Template Literal Types for Strings: For string patterns and
concatenation, template literal types provide type safety that plain
strings cannot.
-
Prefer Type over Interface for Utilities: Use type aliases for
utility types and mapped types, as they're more flexible than interfaces.
-
Test Your Types: Write test cases for complex types using type
assertions to ensure they behave as expected.
-
Avoid Type Gymnastics: Don't create complex types just because you
can. Focus on types that add value and clarity to your code.
-
Use Discriminated Unions: For variant types, use discriminated
unions with a literal type field for better type narrowing.
Common Pitfalls
-
Excessive Type Complexity: Creating overly complex types makes code
harder to understand and can slow down the TypeScript compiler.
-
Ignoring Type Distribution: Forgetting that conditional types
distribute over unions can lead to unexpected type results.
-
Misusing ReturnType with Generics: Using ReturnType on generic
functions without providing type arguments loses type information.
-
Circular Type References: Creating circular type dependencies can
cause TypeScript errors or infinite type recursion.
-
Over-using any: Using any in utility types defeats their purpose and
loses type safety benefits.
-
Not Understanding Mapped Type Modifiers: Misusing + and - modifiers
or forgetting them can produce unexpected readonly/optional behavior.
-
Template Literal Performance: Complex template literal types with
many unions can significantly slow down type checking.
-
Forgetting as const: Not using const assertions when you need literal
types results in widened types that lose specificity.
-
Mismatched Conditional Types: Writing conditional type conditions
that never match or always match makes types useless.
-
Utility Type Overkill: Creating utility types for simple operations
that could be expressed directly makes code harder to read.
When to Use This Skill
Use TypeScript utility types when you need to:
- Transform existing types without duplication
- Create type-safe APIs and libraries
- Build generic, reusable type utilities
- Enforce type constraints at compile time
- Generate types from runtime values
- Create type-safe builders and fluent APIs
- Model complex domain logic with types
- Implement design patterns with type safety
- Reduce type maintenance burden
- Provide better IDE autocomplete and error messages
This skill is essential for library authors, framework developers,
TypeScript experts, and anyone building type-safe, maintainable TypeScript
applications.
Resources
Official Documentation
Learning Resources
Tools and Libraries
Community