The in operator when checking a union
What the in operator does when checking a union type
The in operator checks
whether the given property exists on an object,
and if it does, TypeScript narrows the type to the union variant
where that property is defined.
In simpler terms:
inhelps TypeScript "understand" which exact object type is currently in front of it.
Example 1. The classic case
type User = { name: string };
type Admin = { name: string; permissions: string[] };
function printInfo(person: User | Admin) {
if ("permissions" in person) {
// Here person: Admin
console.log("Permissions:", person.permissions);
} else {
// Here person: User
console.log("User:", person.name);
}
}TypeScript analyzes the union:
Userdoes not containpermissionsAdmincontainspermissions- so inside the
if, TS narrows the type toAdmin
Example 2. Several possible types in a union
type Circle = { radius: number };
type Rectangle = { width: number; height: number };
type Shape = Circle | Rectangle;
function area(shape: Shape) {
if ("radius" in shape) {
// shape: Circle
return Math.PI * shape.radius ** 2;
} else {
// shape: Rectangle
return shape.width * shape.height;
}
}TypeScript understands: if the object has
radius, then it is a Circle, otherwise it is a Rectangle.
Example 3. Complex types with several unique properties
type Cat = { meow: () => void };
type Dog = { bark: () => void };
type Animal = Cat | Dog;
function makeSound(animal: Animal) {
if ("bark" in animal) {
// animal: Dog
animal.bark();
} else {
// animal: Cat
animal.meow();
}
}A great way to tell apart similar types without a discriminant field.
Example 4. Checking nested objects
The in operator also works with nested objects:
type Employee = { info: { position: string } };
type Contractor = { company: string };
function describe(person: Employee | Contractor) {
if ("info" in person) {
console.log("Employee:", person.info.position);
} else {
console.log("Contractor:", person.company);
}
}Important
- The
inoperator does not check the value, only whether the property exists on the object.
const obj = { x: undefined };
console.log("x" in obj); // true (the property exists, even though it is undefined)- The check only works with objects (not primitives):
"length" in "hello"; // true
"x" in 42; // error: number is not an objectWhen to use in
| When | Why |
|---|---|
| In union types with different sets of properties | Great at telling data shapes apart |
When there is no common discriminant field (kind) | An alternative to discriminated unions |
| In custom type guard functions | Can be embedded in return "prop" in obj |
Example of a custom Type Guard based on in
type User = { name: string };
type Admin = { name: string; permissions: string[] };
function isAdmin(person: User | Admin): person is Admin {
return "permissions" in person;
}
function print(person: User | Admin) {
if (isAdmin(person)) {
console.log("Admin:", person.permissions);
} else {
console.log("User:", person.name);
}
}Here
return "permissions" in person;helps TypeScript determine that theisAdminfunction guarantees the typeAdmin.
SUMMARY
What in does | Checks whether the object has the property |
|---|---|
| Syntax | "propertyName" in object |
| Returns | true if the property exists |
| Typing effect | Narrows the union type to the variant where the property exists |
| Works with | Objects and classes |
| Common use | For distinguishing union type variants (`User |
In short:
The
inoperator is a built-in Type Guard that helps TypeScript determine which type from the union is currently in use, by checking whether a given property exists on the object.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.