How to fix Type predicate function does not correctly narrow type in TypeScript
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.
Quick fix
Fixes Type predicate function does not correctly narrow type
// ❌ 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
}- Verify the type predicate syntax is correct
- Ensure predicate logic matches the declared type
- Use explicit type predicates with array methods
- Avoid Object or {} in type predicates
// ❌ Wrong - returns boolean, no narrowingfunction isString(value: unknown): boolean {return typeof value === 'string';}// ✅ Correct - type predicate syntaxfunction 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}Type predicate function does not correctly narrow type
What this error means
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.
Typical symptoms
- TypeScript does not narrow union types after calling your type guard function
- Type remains as the full union inside if/else blocks despite using a predicate
- Error: "A type predicate's type must be assignable to its parameter's type"
- Filter, every, or find array methods do not narrow array element types
- Type predicate works in the true branch but not the false branch
- Compiler error: "Signature must be a type predicate" when returning closures
Common causes
- Missing "is" keyword in return type - returning boolean instead of "value is Type"
- Predicate logic does not match the declared type (e.g., checking for string but declaring as number)
- Using type predicates with Object or {} types, which have known narrowing issues
- Type predicate not biconditional - returns true for multiple unrelated types
- Attempting to use arrow functions directly in filter/every without explicit type predicate declaration
- TypeScript version < 5.5 cannot infer type predicates automatically in many cases
- Type predicate references wrong parameter name or uses "this" type incorrectly
How to fix "Type predicate function does not correctly narrow type"
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.
Advanced notes
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.
Frequently asked questions
- What does "Type predicate function does not correctly narrow type" mean?
- 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.
- How do I fix "Type predicate function does not correctly narrow type"?
- 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.
- What causes "Type predicate function does not correctly narrow type"?
- Missing "is" keyword in return type - returning boolean instead of "value is Type" Predicate logic does not match the declared type (e.g., checking for string but declaring as number) Using type predicates with Object or {} types, which have known narrowing issues
- Is the "Type predicate function does not correctly narrow type" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚TypeScript Official Documentation
- 📖Typescriptlang.org - narrowing.html(Official Docs)
- 🐙Github.com - 37425(GitHub)
- 🐙Github.com - 61740(GitHub)
- 🔗
- 🐙Github.com - 61182(GitHub)
- 🔗