This error occurs when you try to use a function for type narrowing without declaring an assertion signature. TypeScript 3.7+ requires explicit 'asserts' syntax to recognize type narrowing behavior in custom functions.
When you create a custom function to validate or narrow a type, TypeScript doesn't automatically understand that your function performs type narrowing. Unlike built-in TypeScript constructs like `typeof` checks or `instanceof`, custom functions need explicit instructions via an assertion signature. An assertion signature tells TypeScript: "If this function returns without throwing an error, the value has been narrowed to a specific type." Without this signature, TypeScript treats your validation function as a regular function that returns void or boolean, ignoring any type-narrowing intent. This limitation exists because TypeScript can't analyze the runtime behavior of arbitrary JavaScript functions—you must tell it what type narrowing you intend.
Change your function to use an assertion signature with the asserts keyword:
Before (doesn't work):
function isString(value: unknown): boolean {
return typeof value === 'string';
}
const data: unknown = 'hello';
if (isString(data)) {
console.log(data); // Still typed as 'unknown'
}After (works):
function isString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Not a string');
}
}
const data: unknown = 'hello';
isString(data);
console.log(data); // Now typed as 'string'The assertion signature asserts value is string tells TypeScript: "If this function completes, the parameter is definitely a string."
For general conditions that aren't about type narrowing, use asserts condition:
function assertTrue(value: boolean, message?: string): asserts value {
if (!value) {
throw new Error(message || 'Assertion failed');
}
}
function isValidNumber(n: number): asserts n is number {
assertTrue(n > 0, 'Number must be positive');
}
let num: unknown = 42;
isValidNumber(num as number);
console.log(num); // Typed as 'number'The asserts value form (without is) indicates the function just checks a condition and throws if false.
Assertion functions must throw an error if the assertion fails. TypeScript interprets a successful return as proof the type is correct:
function isArray(value: unknown): asserts value is unknown[] {
if (!Array.isArray(value)) {
throw new TypeError(`Expected array, got ${typeof value}`);
}
}
const data: unknown = [1, 2, 3];
isArray(data);
data.map(x => x * 2); // Valid: data is now typed as 'unknown[]'If the function returns without throwing, TypeScript assumes the narrowing is successful. If it returns normally when the condition is false, you'll get runtime errors.
If you're using a third-party validation library like Zod or Yup, wrap it with an assertion function:
import { z } from 'zod';
const userSchema = z.object({
id: z.number(),
name: z.string(),
});
type User = z.infer<typeof userSchema>;
function assertIsUser(value: unknown): asserts value is User {
try {
userSchema.parse(value);
} catch (error) {
throw new Error(`Not a valid user: ${error}`);
}
}
const userData: unknown = JSON.parse(jsonString);
assertIsUser(userData);
console.log(userData.name); // Valid: userData is typed as UserAfter calling your assertion function, the type should be narrowed in TypeScript:
function isString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Not a string');
}
}
function isNumber(value: unknown): asserts value is number {
if (typeof value !== 'number') {
throw new Error('Not a number');
}
}
const mixed: unknown = 'test';
isString(mixed);
console.log(mixed.toUpperCase()); // Valid: mixed is string
// After this line, TypeScript knows mixed can't be a number:
const checkType = (x: number) => x + 1;
// checkType(mixed); // Error: mixed is string, not numberHover over the variable in your IDE after the assertion call—the type should show the narrowed type, not 'unknown'.
### Difference Between Type Predicates and Assertion Functions
TypeScript offers two ways to create custom type narrowing:
Type Predicates (return boolean):
function isString(value: unknown): value is string {
return typeof value === 'string';
}
if (isString(data)) {
// data is string
}Assertion Functions (return void/asserts):
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') throw new Error();
}
assertIsString(data);
// data is stringUse type predicates in conditionals (if, filter). Use assertion functions for runtime validations that should fail loudly.
### Combining with instanceof
For class instances, use asserts with instanceof:
class User {
constructor(public name: string) {}
}
function assertIsUser(value: unknown): asserts value is User {
if (!(value instanceof User)) {
throw new Error('Not a User instance');
}
}
const obj: unknown = new User('Alice');
assertIsUser(obj);
console.log(obj.name); // Valid### Type Guards in Arrays
For filtering arrays, use type predicates instead:
function isString(value: unknown): value is string {
return typeof value === 'string';
}
const mixed: (string | number)[] = ['a', 1, 'b', 2];
const strings = mixed.filter(isString);
// strings is string[]Assertion functions narrowing persists after the function call across the scope, whereas type predicates only narrow within if blocks or after filtering.
### Performance Note
Assertion functions are checked at compile-time but the runtime checks (throw statements) always execute. Use them for critical validations. For performance-sensitive code at scale, consider lazy validation or caching results.
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