Difficulty: Intermediate
The `instanceof` operator narrows types based on the prototype chain. When you write `x instanceof SomeClass`, TypeScript narrows `x` to `SomeClass` in the true branch. This works with any class, including built-in classes like `Date`, `RegExp`, `Error`, and custom classes. It is the primary way to narrow types when working with class hierarchies.
The `instanceof` check works at runtime by traversing the prototype chain of the object. If the constructor function's `prototype` property appears anywhere in the chain, the check returns true. This means it works with inheritance - an instance of a subclass is also an instance of its parent class. TypeScript's type narrowing respects this relationship.
The `in` operator checks whether a property name exists in an object. When you write `'propertyName' in obj`, TypeScript narrows the type of `obj` to whichever members of a union type contain that property. This is particularly useful for discriminating between object types in a union without needing a dedicated discriminant property.
The `in` operator is a pragmatic alternative to discriminated unions when you are working with types that don't share a common tag property. For example, if you have a union of `Bird | Fish` where Bird has a `fly` method and Fish has a `swim` method, you can use `'fly' in animal` to narrow to Bird. This technique is common when integrating with external APIs or libraries whose types you cannot modify.
class HttpError {
constructor(public status: number, public message: string) {}
}
class ValidationError {
constructor(public field: string, public message: string) {}
}
function handleError(error: HttpError | ValidationError): string {
if (error instanceof HttpError) {
return `HTTP ${error.status}: ${error.message}`;
}
return `Validation error on '${error.field}': ${error.message}`;
}
console.log(handleError(new HttpError(404, "Not Found")));
console.log(handleError(new ValidationError("email", "Invalid format")));
After the instanceof check, TypeScript knows the error is an HttpError in the if branch and a ValidationError in the else branch, providing access to their specific properties.
type Bird = { fly: () => string; name: string };
type Fish = { swim: () => string; name: string };
function move(animal: Bird | Fish): string {
if ("fly" in animal) {
return animal.fly();
}
return animal.swim();
}
const eagle: Bird = { name: "Eagle", fly: () => "Soaring through the sky" };
const salmon: Fish = { name: "Salmon", swim: () => "Swimming upstream" };
console.log(move(eagle));
console.log(move(salmon));
The `in` operator checks for the presence of the `fly` property. TypeScript narrows to Bird in the true branch and Fish in the false branch.
class ApiResponse {
constructor(public data: unknown) {}
}
function processResponse(response: ApiResponse): string {
const data = response.data;
if (data instanceof Array) {
return `Array with ${data.length} items`;
}
if (typeof data === "object" && data !== null && "message" in data) {
return `Message: ${(data as { message: string }).message}`;
}
return `Raw: ${String(data)}`;
}
console.log(processResponse(new ApiResponse([1, 2, 3])));
console.log(processResponse(new ApiResponse({ message: "Success" })));
console.log(processResponse(new ApiResponse("text")));
This example combines instanceof (for Array) and in (for checking object shape) to safely narrow unknown data into specific types.
instanceof operator, in operator, class narrowing, property checking