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/asymmetric-al-typescript-advanced-types/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 384% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-17 | ✓→✗ | ▼ Worse | 481% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 561% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 474% | 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 | fail→pass | 6,395 | 5,866 | -8% | 1 | 1 | 0% | 1,251 | 6,060 | +384% | 0 | 0 | — |
case-02 | pass→pass | 5,022 | 5,934 | +18% | 1 | 1 | 0% | 928 | 6,137 | +561% | 0 | 0 | — |
case-03 | pass→pass | 5,277 | 4,616 | -13% | 1 | 1 | 0% | 1,029 | 5,909 | +474% | 0 | 0 | — |
case-04 | pass→pass | 8,342 | 7,426 | -11% | 1 | 1 | 0% | 1,616 | 6,353 | +293% | 0 | 0 | — |
case-05 | pass→pass | 15,825 | 11,130 | -30% | 1 | 1 | 0% | 3,010 | 7,070 | +135% | 0 | 0 | — |
case-06 | pass→pass | 14,107 | 14,446 | +2% | 1 | 1 | 0% | 2,708 | 7,993 | +195% | 0 | 0 | — |
case-07 | fail→pass | 23,567 | 12,423 | -47% | 1 | 1 | 0% | 4,370 | 7,350 | +68% | 0 | 0 | — |
case-08 | pass→pass | 18,696 | 20,626 | +10% | 1 | 1 | 0% | 3,463 | 8,949 | +158% | 0 | 0 | — |
case-09 | pass→pass | 18,178 | 12,410 | -32% | 1 | 1 | 0% | 3,336 | 7,195 | +116% | 0 | 0 | — |
case-10 | pass→pass | 33,160 | 31,985 | -4% | 1 | 1 | 0% | 5,919 | 10,632 | +80% | 0 | 0 | — |
case-11 | fail→fail | 14,903 | 12,870 | -14% | 1 | 1 | 0% | 2,810 | 7,636 | +172% | 0 | 0 | — |
case-12 | pass→pass | 7,967 | 6,716 | -16% | 1 | 1 | 0% | 1,570 | 6,256 | +298% | 0 | 0 | — |
case-13 | pass→pass | 6,932 | 4,935 | -29% | 1 | 1 | 0% | 1,187 | 5,808 | +389% | 0 | 0 | — |
case-14 | pass→pass | 10,035 | 7,005 | -30% | 1 | 1 | 0% | 1,793 | 6,319 | +252% | 0 | 0 | — |
case-15 | pass→pass | 4,414 | 4,504 | +2% | 1 | 1 | 0% | 709 | 5,771 | +714% | 0 | 0 | — |
case-16 | fail→fail | 8,628 | 10,411 | +21% | 1 | 1 | 0% | 1,486 | 6,812 | +358% | 0 | 0 | — |
case-17 | pass→fail | 6,027 | 6,531 | +8% | 1 | 1 | 0% | 1,062 | 6,171 | +481% | 0 | 0 | — |
case-18 | pass→pass | 11,368 | 13,734 | +21% | 1 | 1 | 0% | 2,179 | 7,616 | +250% | 0 | 0 | — |
case-19 | pass→pass | 5,215 | 5,650 | +8% | 1 | 1 | 0% | 922 | 6,026 | +554% | 0 | 0 | — |
case-20 | fail→fail | 4,190 | 4,354 | +4% | 1 | 1 | 0% | 841 | 5,842 | +595% | 0 | 0 | — |
case-21 | pass→pass | 15,088 | 17,734 | +18% | 1 | 1 | 0% | 2,764 | 8,418 | +205% | 0 | 0 | — |
case-22 | pass→pass | 4,627 | 4,700 | +2% | 1 | 1 | 0% | 805 | 5,752 | +615% | 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 +5 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.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.