This error occurs when a type predicate function fails to narrow types as expected, usually because the predicate logic is incorrect, the return type lacks the "is" syntax, or TypeScript cannot infer the narrowing automatically.
Type predicates in TypeScript use the `parameterName is Type` syntax to tell the compiler that a function narrows a type. When a type predicate "does not narrow correctly," it means TypeScript either cannot perform the narrowing you expect or the predicate's logic contradicts its declared type. This happens because TypeScript expects type predicates to be "biconditional" - meaning if the predicate returns true, the value IS that type, and if it returns false, the value IS NOT that type. When your predicate logic doesn't follow this if-and-only-if relationship, TypeScript may fail to narrow correctly or produce unexpected type inference. Additionally, TypeScript will never automatically infer a function as a type guard unless specific conditions are met (TypeScript 5.5+ can infer predicates for simple cases). If you return a boolean without the type predicate syntax, the compiler treats it as a regular boolean, not a type-narrowing operation.
Ensure your function return type uses parameterName is Type syntax, not just boolean:
// ❌ Wrong - returns boolean, no narrowing
function isString(value: unknown): boolean {
return typeof value === 'string';
}
// ✅ Correct - type predicate syntax
function isString(value: unknown): value is string {
return typeof value === 'string';
}
const x: string | number = 'hello';
if (isString(x)) {
console.log(x.toUpperCase()); // x is narrowed to string
}The parameter name in the predicate must match the function parameter exactly.
The runtime check must accurately determine if the value is the declared type:
interface User {
id: number;
name: string;
}
// ❌ Wrong - checks for name but declares as User
function isUser(value: unknown): value is User {
return typeof (value as any).name === 'string';
}
// ✅ Correct - checks all required properties
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
typeof (value as any).id === 'number' &&
typeof (value as any).name === 'string'
);
}Incomplete checks lead to false positives and TypeScript may not trust the narrowing.
Array methods like filter require explicit type guard functions, not inline checks:
const items: (string | null)[] = ['hello', null, 'world', null];
// ❌ Wrong - TypeScript doesn't narrow automatically
const filtered = items.filter(item => item !== null);
// Type: (string | null)[]
// ✅ Correct - explicit type predicate function
function isNotNull<T>(value: T | null): value is T {
return value !== null;
}
const filtered = items.filter(isNotNull);
// Type: string[]For TypeScript 5.5+, simple arrow functions can be inferred in some cases, but explicit predicates are more reliable.
TypeScript has known issues narrowing Object and empty object types:
// ❌ Problematic - Object type predicate doesn't narrow correctly
function isObject(value: unknown): value is Object {
return typeof value === 'object' && value !== null;
}
const x: number | object = 42;
if (isObject(x)) {
// Type may be inferred as number | Object (incorrect)
}
// ✅ Better - use specific object types or unknown
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}See [TypeScript Issue #37425](https://github.com/microsoft/TypeScript/issues/37425) for details on this limitation.
Type predicates must be true if-and-only-if the value is that type:
// ❌ Wrong - not biconditional
// Returns true for small numbers, false for strings AND large numbers
function isSmallNumber(value: string | number): value is number {
return typeof value === 'number' && value < 100;
}
// ✅ Correct - separates the type check from the value check
function isNumber(value: string | number): value is number {
return typeof value === 'number';
}
function isSmallNumber(value: number): boolean {
return value < 100;
}If your predicate combines type checking with value validation, split them into separate functions.
TypeScript 5.5 introduced automatic type predicate inference for simple cases:
// TypeScript 5.5+ can infer this as a type predicate
function isString(value: unknown) {
return typeof value === 'string';
}
// Automatically inferred as: value is string
const items = ['a', 1, 'b', 2].filter(isString);
// Type: string[]Update your TypeScript version:
npm install typescript@latestCheck your version:
npx tsc --versionFor older versions, always use explicit type predicate syntax.
Type Predicate Inference Rules (TS 5.5+):
Type predicates can only be inferred when:
- No explicit return type is specified
- The function has a single return statement (no implicit returns)
- The logic is a simple type check TypeScript can analyze
Biconditional vs One-Way Predicates:
TypeScript assumes predicates are biconditional (a ↔️ b), but some use cases need one-way narrowing (a → b). See [Issue #61740](https://github.com/microsoft/TypeScript/issues/61740) for discussion on supporting non-biconditional predicates.
Using "this" in Type Predicates:
When using type predicates with class methods that reference this, ensure the parameter name matches exactly and this appears in the correct position. See [Issue #16021](https://github.com/Microsoft/TypeScript/issues/16021) for known issues.
Generic Type Predicates:
When creating generic type guards, ensure the predicate type is assignable to the parameter type:
function isType<T>(value: unknown, check: (v: any) => boolean): value is T {
return check(value);
}This pattern can cause assignability errors. Use conditional types or overloads for safer generic guards.
Debugging Type Narrowing:
Use the TypeScript playground or hover over variables in your IDE to see what type TypeScript infers after your predicate. This helps identify where narrowing fails.
Function 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