Designs complex generic types, refactors `any` types to strict alternatives, creates type guards and utility types, and resolves TypeScript compiler errors. Use when the user asks about TypeScript (TS) types, generics, type inference, type guards, removing `any` types, strict typing, type errors, `infer`, `extends`, conditional types, mapped types, template literal types, branded/opaque types, or utility types like `Partial`, `Record`, `ReturnType`, and `Awaited`.
97
97%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Conditional types provide if/else logic at the type level. They use the extends keyword to check type relationships and return different types based on the result.
type Conditional = SomeType extends OtherType ? TrueType : FalseType;The condition checks if SomeType is assignable to OtherType.
// Check if type is string
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
type Test3 = IsString<"hello">; // true (literal extends string)
// Check type relationships
type Result1 = string extends string ? "yes" : "no"; // "yes"
type Result2 = string extends number ? "yes" : "no"; // "no"
type Result3 = "hello" extends string ? "yes" : "no"; // "yes"type IsNullable<T> = null extends T ? true : false;
type Test1 = IsNullable<string | null>; // true
type Test2 = IsNullable<string>; // false
type Test3 = IsNullable<undefined>; // false (null !== undefined)// Return different types based on input
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 T1 = TypeName<string>; // "string"
type T2 = TypeName<number>; // "number"
type T3 = TypeName<() => void>; // "function"
type T4 = TypeName<{ a: 1 }>; // "object"When a conditional type acts on a union, it distributes over each member:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// Distributes to: ToArray<string> | ToArray<number>
// Result: string[] | number[]
// NOT: (string | number)[]Wrap in tuple to prevent distribution:
type ToArrayNonDistributive<T> = [T] extends [any] ? T[] : never;
type Result = ToArrayNonDistributive<string | number>;
// Result: (string | number)[]interface BaseRouterConfig {
search?: string[];
}
type TupleToSearchParams<T extends string[]> = {
[K in T[number]]?: string;
};
// Only convert if search is defined and is a string array
type SearchParams<TConfig extends BaseRouterConfig, TRoute extends keyof TConfig> =
TConfig[TRoute]["search"] extends string[]
? TupleToSearchParams<TConfig[TRoute]["search"]>
: undefined;const makeRouter = <TConfig extends Record<string, { search?: string[] }>>(
config: TConfig
) => {
return {
goTo: <TRoute extends keyof TConfig>(
route: TRoute,
// Only allow search params if route has search defined
search?: TConfig[TRoute]["search"] extends string[]
? { [K in TConfig[TRoute]["search"][number]]?: string }
: never
) => {
// Implementation
},
};
};
const router = makeRouter({
"/": {},
"/search": { search: ["query", "page"] },
});
router.goTo("/"); // No search param allowed
router.goTo("/search", { query: "test", page: "1" }); // Search params requiredUse never to filter out types:
type ExtractStrings<T> = T extends string ? T : never;
type Mixed = "a" | "b" | 1 | 2 | true;
type OnlyStrings = ExtractStrings<Mixed>; // "a" | "b"This is how Extract and Exclude utilities work:
// Built-in utility implementations
type Extract<T, U> = T extends U ? T : never;
type Exclude<T, U> = T extends U ? never : T;type DeepReadonly<T> = T extends Function
? T
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
interface User {
name: string;
address: {
city: string;
country: string;
};
greet: () => void;
}
type ReadonlyUser = DeepReadonly<User>;
// All properties including nested ones are readonly
// Functions remain unchanged// Check if array type is empty
type IsEmptyArray<T extends any[]> = T extends []
? true
: T extends [any, ...any[]]
? false
: boolean; // Unknown length arrays
type Test1 = IsEmptyArray<[]>; // true
type Test2 = IsEmptyArray<[1]>; // false
type Test3 = IsEmptyArray<string[]>; // boolean (unknown at compile time)// Ensure array has at least one element
type NonEmptyArray<T extends any[]> = T extends [infer First, ...infer Rest]
? [First, ...Rest]
: never;
type Config = {
fields: ["name", "email"]; // Non-empty
};
// Use in conditional
type HasFields<T extends { fields?: string[] }> =
T["fields"] extends [string, ...string[]]
? true
: false;type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Test1 = UnwrapPromise<Promise<string>>; // string
type Test2 = UnwrapPromise<string>; // string (passthrough)type UnwrapArray<T> = T extends (infer U)[] ? U : T;
type Test1 = UnwrapArray<string[]>; // string
type Test2 = UnwrapArray<number>; // number (passthrough)type Nullable<T> = T extends object
? { [K in keyof T]: T[K] | null }
: T | null;type WrongIsArray<T> = T extends any[] ? true : false;
type Test = WrongIsArray<string | number[]>; // boolean (distributes!)
// If you want to check the entire union:
type CorrectIsArray<T> = [T] extends [any[]] ? true : false;
type Test2 = CorrectIsArray<string | number[]>; // falseSometimes union types or overloads are simpler:
// Over-complicated
type ProcessResult<T> = T extends string
? { type: "string"; value: string }
: T extends number
? { type: "number"; value: number }
: never;
// Simpler with discriminated union
type Result =
| { type: "string"; value: string }
| { type: "number"; value: number };// Always provide a sensible false branch
type ExtractName<T> = T extends { name: infer N } ? N : never;
// Consider: what happens when T doesn't have name?
type Test = ExtractName<{ age: number }>; // never