This TypeScript error occurs when you attempt to define a conditional type without using the required 'extends' keyword. Conditional types must follow the syntax 'T extends U ? X : Y' to specify the condition being tested.
TypeScript conditional types are a powerful feature that allow you to create types that change based on a condition, similar to ternary operators in JavaScript. They follow the syntax `T extends U ? X : Y`, where TypeScript checks if type T is assignable to type U, and resolves to type X if true, or type Y if false. This error appears when you try to use conditional type syntax without including the required `extends` keyword. The `extends` clause is mandatory because it defines the actual condition being tested. Without it, TypeScript cannot determine what comparison to make or when to apply the true/false branches. The error is particularly common when working with the `infer` keyword, which is only permitted within the `extends` clause of a conditional type. The `infer` keyword allows you to declare a type variable within the condition and use it in the true branch, but it must appear to the right of the `extends` keyword.
Conditional types must use the extends keyword to define what condition is being tested. Fix the syntax by adding extends between your type parameter and the condition:
// WRONG - missing 'extends' keyword
type IsString<T> = T string ? true : false;
// CORRECT - proper conditional type syntax
type IsString<T> = T extends string ? true : false;
// Usage
type Test1 = IsString<"hello">; // true
type Test2 = IsString<number>; // falseThe basic pattern is always: T extends U ? TrueType : FalseType
The infer keyword can only be used within the extends clause of a conditional type. It must appear to the right of the extends keyword:
// WRONG - infer used without extends
type GetElementType<T> = T (infer U)[] ? U : never;
// CORRECT - infer within extends clause
type GetElementType<T> = T extends (infer U)[] ? U : never;
// Usage: Extract element type from arrays
type StringArray = string[];
type ElementType = GetElementType<StringArray>; // string
// WRONG - infer in the wrong position
type ReturnType<T> = (infer R) T extends (...args: any[]) => any ? R : never;
// CORRECT - infer on the right side of extends
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;The infer keyword declares a type variable that captures part of the matched type.
Conditional types follow a specific structure that mirrors JavaScript ternary operators but with type checking:
// Basic structure: T extends U ? X : Y
type Example<T> = T extends TargetType ? MatchType : NonMatchType;
// Real-world example: Extract function return type
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function greet(): string {
return "hello";
}
type GreetReturn = GetReturnType<typeof greet>; // string
// Example: Check if type has a name property
type HasName<T> = T extends { name: string } ? true : false;
type Person = { name: string; age: number };
type User = { id: number };
type PersonHasName = HasName<Person>; // true
type UserHasName = HasName<User>; // falseThe extends keyword checks if the type on the left is assignable to the type on the right.
When nesting conditional types, each level must have its own extends clause:
// WRONG - missing extends in nested condition
type Unwrap<T> = T extends Promise<infer U>
? U Promise<any> ? Unwrap<U> : U
: T;
// CORRECT - each condition has extends
type Unwrap<T> = T extends Promise<infer U>
? U extends Promise<any> ? Unwrap<U> : U
: T;
// Usage: Recursively unwrap nested Promises
type SinglePromise = Promise<string>;
type NestedPromise = Promise<Promise<Promise<number>>>;
type Unwrapped1 = Unwrap<SinglePromise>; // string
type Unwrapped2 = Unwrap<NestedPromise>; // number
// Another example: Flatten array types
type Flatten<T> = T extends (infer U)[]
? U extends any[] ? Flatten<U> : U
: T;
type NestedArray = string[][][];
type Flattened = Flatten<NestedArray>; // stringEach conditional branch can contain another conditional type with its own extends clause.
Conditional types distribute over union types automatically when used with type parameters. This behavior requires correct extends syntax:
// Distributes over unions automatically
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumber = string | number;
type Result = ToArray<StringOrNumber>; // string[] | number[]
// To prevent distribution, wrap in brackets
type ToArrayNonDistributive<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDistributive<StringOrNumber>; // (string | number)[]
// Real example: Filter out null/undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string
// Example: Extract function types from union
type ExtractFunction<T> = T extends (...args: any[]) => any ? T : never;
type Mixed = string | (() => void) | number | ((x: number) => string);
type OnlyFunctions = ExtractFunction<Mixed>; // (() => void) | ((x: number) => string)Distribution applies the conditional type to each member of a union separately.
### Understanding Type Assignability
The extends keyword in conditional types checks assignability, not inheritance. A extends B means "can a value of type A be assigned to a variable of type B?"
// Structural typing example
type HasId = { id: number };
type User = { id: number; name: string };
type CanAssign = User extends HasId ? true : false; // true
// User has all properties of HasId plus more### Variance and Conditional Types
Conditional types can create complex variance relationships:
// Contravariance with function parameters
type GetParam<T> = T extends (param: infer P) => any ? P : never;
type Fn = (x: string) => void;
type Param = GetParam<Fn>; // string
// Multiple infer positions
type GetBoth<T> = T extends (param: infer P) => infer R
? { param: P; return: R }
: never;
type BothTypes = GetBoth<(x: number) => string>;
// { param: number; return: string }### Conditional Types vs Type Guards
Don't confuse conditional types (compile-time) with type guards (runtime):
// COMPILE-TIME: Conditional type
type IsString<T> = T extends string ? true : false;
// RUNTIME: Type guard function
function isString(value: unknown): value is string {
return typeof value === "string";
}### Template Literal Types with Conditional Types
Combine conditional types with template literals for powerful patterns:
// Extract method names that start with "get"
type GetterKeys<T> = {
[K in keyof T]: K extends `get${string}` ? K : never
}[keyof T];
type Example = {
getName: () => string;
getAge: () => number;
setName: (s: string) => void;
id: number;
};
type Getters = GetterKeys<Example>; // "getName" | "getAge"### Recursive Conditional Types
TypeScript supports recursive conditional types with depth limits:
// Deep readonly implementation
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
type Mutable = {
user: {
name: string;
address: {
city: string;
};
};
};
type Immutable = DeepReadonly<Mutable>;
// All properties at all levels are readonlyFunction expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript