Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects.
.claude/skills/microck-typescript-advanced-types/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✓→✗ | ▼ Worse | 164% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 146% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 159% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 237% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 322% | 0% |
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.
Purpose: Create reusable, type-flexible components while maintaining type safety.
Basic Generic Function:
typescriptfunction identity<T>(value: T): T { return value; } const num = identity<number>(42); // Type: number const str = identity<string>("hello"); // Type: string const auto = identity(true); // Type inferred: boolean
Generic Constraints:
typescriptinterface HasLength { length: number; } function logLength<T extends HasLength>(item: T): T { console.log(item.length); return item; } logLength("hello"); // OK: string has length logLength([1, 2, 3]); // OK: array has length logLength({ length: 10 }); // OK: object has length // logLength(42); // Error: number has no length
Multiple Type Parameters:
typescriptfunction merge<T, U>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 }; } const merged = merge( { name: "John" }, { age: 30 } ); // Type: { name: string } & { age: number }
Purpose: Create types that depend on conditions, enabling sophisticated type logic.
Basic Conditional Type:
typescripttype IsString<T> = T extends string ? true : false; type A = IsString<string>; // true type B = IsString<number>; // false
Extracting Return Types:
typescripttype ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; function getUser() { return { id: 1, name: "John" }; } type User = ReturnType<typeof getUser>; // Type: { id: number; name: string; }
Distributive Conditional Types:
typescripttype ToArray<T> = T extends any ? T[] : never; type StrOrNumArray = ToArray<string | number>; // Type: string[] | number[]
Nested Conditions:
typescripttype 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>; // "string" type T2 = TypeName<() => void>; // "function"
Purpose: Transform existing types by iterating over their properties.
Basic Mapped Type:
typescripttype Readonly<T> = { readonly [P in keyof T]: T[P]; }; interface User { id: number; name: string; } type ReadonlyUser = Readonly<User>; // Type: { readonly id: number; readonly name: string; }
Optional Properties:
typescripttype Partial<T> = { [P in keyof T]?: T[P]; }; type PartialUser = Partial<User>; // Type: { id?: number; name?: string; }
Key Remapping:
typescripttype Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] }; interface Person { name: string; age: number; } type PersonGetters = Getters<Person>; // Type: { getName: () => string; getAge: () => number; }
Filtering Properties:
typescripttype 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>; // Type: { id: number; age: number; }
Purpose: Create string-based types with pattern matching and transformation.
Basic Template Literal:
typescripttype EventName = "click" | "focus" | "blur"; type EventHandler = `on${Capitalize<EventName>}`; // Type: "onClick" | "onFocus" | "onBlur"
String Manipulation:
typescripttype UppercaseGreeting = Uppercase<"hello">; // "HELLO" type LowercaseGreeting = Lowercase<"HELLO">; // "hello" type CapitalizedName = Capitalize<"john">; // "John" type UncapitalizedName = Uncapitalize<"John">; // "john"
Path Building:
typescripttype 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>; // Type: "server" | "database" | "server.host" | "server.port" | "database.url"
Built-in Utility Types:
typescript// Partial<T> - Make all properties optional type PartialUser = Partial<User>; // Required<T> - Make all properties required type RequiredUser = Required<PartialUser>; // Readonly<T> - Make all properties readonly type ReadonlyUser = Readonly<User>; // Pick<T, K> - Select specific properties type UserName = Pick<User, "name" | "email">; // Omit<T, K> - Remove specific properties type UserWithoutPassword = Omit<User, "password">; // Exclude<T, U> - Exclude types from union type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c" // Extract<T, U> - Extract types from union type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b" // NonNullable<T> - Exclude null and undefined type T3 = NonNullable<string | null | undefined>; // string // Record<K, T> - Create object type with keys K and values T type PageInfo = Record<"home" | "about", { title: string }>;
typescripttype 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); // Type-safe! }); emitter.emit("user:created", { id: "1", name: "John" }); // emitter.emit("user:created", { id: "1" }); // Error: missing 'name'
typescripttype HTTPMethod = "GET" | "POST" | "PUT" | "DELETE"; type EndpointConfig = { "/users": { GET: { response: User[] }; POST: { body: { name: string; email: string }; response: User }; }; "/users/:id": { GET: { params: { id: string }; response: User }; PUT: { params: { id: string }; body: Partial<User>; response: User }; DELETE: { params: { id: string }; response: void }; }; }; type ExtractParams<T> = T extends { params: infer P } ? P : never; type ExtractBody<T> = T extends { body: infer B } ? B : never; type ExtractResponse<T> = T extends { response: infer R } ? R : never; class APIClient<Config extends Record<string, Record<HTTPMethod, any>>> { async request< Path extends keyof Config, Method extends keyof Config[Path] >( path: Path, method: Method, ...[options]: ExtractParams<Config[Path][Method]> extends never ? ExtractBody<Config[Path][Method]> extends never ? [] : [{ body: ExtractBody<Config[Path][Method]> }] : [{ params: ExtractParams<Config[Path][Method]>; body?: ExtractBody<Config[Path][Method]>; }] ): Promise<ExtractResponse<Config[Path][Method]>> { // Implementation here return {} as any; } } const api = new APIClient<EndpointConfig>(); // Type-safe API calls const users = await api.request("/users", "GET"); // Type: User[] const newUser = await api.request("/users", "POST", { body: { name: "John", email: "john@example.com" } }); // Type: User const user = await api.request("/users/:id", "GET", { params: { id: "123" } }); // Type: User
typescripttype BuilderState<T> = { [K in keyof T]: T[K] | undefined; }; type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K; }[keyof T]; type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never; }[keyof T]; type IsComplete<T, S> = RequiredKeys<T> extends keyof S ? S[RequiredKeys<T>] extends undefined ? false : true : false; class Builder<T, S extends BuilderState<T> = {}> { private state: S = {} as S; set<K extends keyof T>( key: K, value: T[K] ): Builder<T, S & Record<K, T[K]>> { this.state[key] = value; return this as any; } build( this: IsComplete<T, S> extends true ? this : never ): T { return this.state as T; } } interface User { id: string; name: string; email: string; age?: number; } const builder = new Builder<User>(); const user = builder .set("id", "1") .set("name", "John") .set("email", "john@example.com") .build(); // OK: all required fields set // const incomplete = builder // .set("id", "1") // .build(); // Error: missing required fields
typescripttype 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; }; }; database: { url: string; pool: { min: number; max: number; }; }; } type ReadonlyConfig = DeepReadonly<Config>; // All nested properties are readonly type PartialConfig = DeepPartial<Config>; // All nested properties are optional
typescripttype ValidationRule<T> = { validate: (value: T) => boolean; message: string; }; type FieldValidation<T> = { [K in keyof T]?: ValidationRule<T[K]>[]; }; type ValidationErrors<T> = { [K in keyof T]?: string[]; }; class FormValidator<T extends Record<string, any>> { constructor(private rules: FieldValidation<T>) {} validate(data: T): ValidationErrors<T> | null { const errors: ValidationErrors<T> = {}; let hasErrors = false; for (const key in this.rules) { const fieldRules = this.rules[key]; const value = data[key]; if (fieldRules) { const fieldErrors: string[] = []; for (const rule of fieldRules) { if (!rule.validate(value)) { fieldErrors.push(rule.message); } } if (fieldErrors.length > 0) { errors[key] = fieldErrors; hasErrors = true; } } } return hasErrors ? errors : null; } } interface LoginForm { email: string; password: string; } const validator = new FormValidator<LoginForm>({ email: [ { validate: (v) => v.includes("@"), message: "Email must contain @" }, { validate: (v) => v.length > 0, message: "Email is required" } ], password: [ { validate: (v) => v.length >= 8, message: "Password must be at least 8 characters" } ] }); const errors = validator.validate({ email: "invalid", password: "short" }); // Type: { email?: string[]; password?: string[]; } | null
typescripttype 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); // Type: T break; case "error": console.log(state.error); // Type: string break; case "loading": console.log("Loading..."); break; } } // Type-safe state machine type State = | { type: "idle" } | { type: "fetching"; requestId: string } | { type: "success"; data: any } | { type: "error"; error: Error }; type Event = | { type: "FETCH"; requestId: string } | { type: "SUCCESS"; data: any } | { type: "ERROR"; error: Error } | { type: "RESET" }; function reducer(state: State, event: Event): State { switch (state.type) { case "idle": return event.type === "FETCH" ? { type: "fetching", requestId: event.requestId } : state; case "fetching": if (event.type === "SUCCESS") { return { type: "success", data: event.data }; } if (event.type === "ERROR") { return { type: "error", error: event.error }; } return state; case "success": case "error": return event.type === "RESET" ? { type: "idle" } : state; } }
typescript// Extract array element type type ElementType<T> = T extends (infer U)[] ? U : never; type NumArray = number[]; type Num = ElementType<NumArray>; // number // Extract promise type type PromiseType<T> = T extends Promise<infer U> ? U : never; type AsyncNum = PromiseType<Promise<number>>; // number // Extract function parameters type Parameters<T> = T extends (...args: infer P) => any ? P : never; function foo(a: string, b: number) {} type FooParams = Parameters<typeof foo>; // [string, number]
typescriptfunction 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()); // Type: string[] }
typescriptfunction assertIsString(value: unknown): asserts value is string { if (typeof value !== "string") { throw new Error("Not a string"); } } function processValue(value: unknown) { assertIsString(value); // value is now typed as string console.log(value.toUpperCase()); }
unknown over any: Enforce type checkinginterface for object shapes: Better error messagestype for unions and complex types: More flexibletypescript// Type assertion tests type AssertEqual<T, U> = [T] extends [U] ? [U] extends [T] ? true : false : false; type Test1 = AssertEqual<string, string>; // true type Test2 = AssertEqual<string, number>; // false type Test3 = AssertEqual<string | number, string>; // false // Expect error helper type ExpectError<T extends never> = T; // Example usage type ShouldError = ExpectError<AssertEqual<string, number>>;
any: Defeats the purpose of TypeScript| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 17,788 | 17,992 | +1% | 1 | 1 | 0% | 3,493 | 8,580 | +146% | 0 | 0 | — |
case-02 | pass→pass | 14,219 | 12,499 | -12% | 1 | 1 | 0% | 2,914 | 7,533 | +159% | 0 | 0 | — |
case-03 | fail→fail | 15,955 | 15,707 | -2% | 1 | 1 | 0% | 3,303 | 8,505 | +157% | 0 | 0 | — |
case-04 | pass→pass | 9,395 | 4,908 | -48% | 1 | 1 | 0% | 1,753 | 5,904 | +237% | 0 | 0 | — |
case-05 | pass→pass | 7,725 | 7,692 | -0% | 1 | 1 | 0% | 1,557 | 6,564 | +322% | 0 | 0 | — |
case-06 | pass→fail | 15,526 | 16,795 | +8% | 1 | 1 | 0% | 3,169 | 8,367 | +164% | 0 | 0 | — |
case-07 | pass→pass | 13,946 | 13,818 | -1% | 1 | 1 | 0% | 2,732 | 7,927 | +190% | 0 | 0 | — |
case-08 | pass→pass | 15,995 | 9,957 | -38% | 1 | 1 | 0% | 3,497 | 7,162 | +105% | 0 | 0 | — |
case-09 | pass→pass | 12,584 | 11,570 | -8% | 1 | 1 | 0% | 2,608 | 7,350 | +182% | 0 | 0 | — |
case-10 | pass→pass | 6,069 | 5,682 | -6% | 1 | 1 | 0% | 1,097 | 6,095 | +456% | 0 | 0 | — |
case-11 | pass→pass | 5,459 | 4,737 | -13% | 1 | 1 | 0% | 1,007 | 5,896 | +486% | 0 | 0 | — |
case-12 | pass→pass | 8,531 | 8,300 | -3% | 1 | 1 | 0% | 1,590 | 6,576 | +314% | 0 | 0 | — |
case-17 | pass→pass | 14,759 | 8,530 | -42% | 1 | 1 | 0% | 2,769 | 6,552 | +137% | 0 | 0 | — |
case-13 | pass→pass | 6,534 | 5,134 | -21% | 1 | 1 | 0% | 1,213 | 6,019 | +396% | 0 | 0 | — |
case-14 | pass→pass | 12,178 | 10,439 | -14% | 1 | 1 | 0% | 2,448 | 7,000 | +186% | 0 | 0 | — |
case-15 | fail→fail | 11,262 | 9,703 | -14% | 1 | 1 | 0% | 2,037 | 6,920 | +240% | 0 | 0 | — |
case-16 | pass→pass | 5,508 | 4,816 | -13% | 1 | 1 | 0% | 1,020 | 5,974 | +486% | 0 | 0 | — |
case-22 | pass→pass | 9,438 | 8,254 | -13% | 1 | 1 | 0% | 1,850 | 6,709 | +263% | 0 | 0 | — |
case-18 | pass→pass | 4,291 | 3,611 | -16% | 1 | 1 | 0% | 771 | 5,678 | +636% | 0 | 0 | — |
case-19 | pass→pass | 7,170 | 5,122 | -29% | 1 | 1 | 0% | 1,481 | 5,988 | +304% | 0 | 0 | — |
case-20 | fail→fail | 6,991 | 5,320 | -24% | 1 | 1 | 0% | 1,390 | 6,116 | +340% | 0 | 0 | — |
case-21 | pass→pass | 6,206 | 5,261 | -15% | 1 | 1 | 0% | 1,255 | 6,067 | +383% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of -100 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Other measured skills in the registry, with their headline benchmark lift.