in in Operator Narrowing
Last updated: 2026-10-22
in in Operator Narrowing
The in operator checks whether a property name exists on an object, and TypeScript narrows the union to the member that has that property. When union members share no discriminant field and are interfaces (so instanceof is unavailable), in is the handiest way to tell shapes apart.
| Group | Narrowing & Assertions |
|---|---|
| Kind | Operator |
| Strict mode | Not required |
ℹ️ Info The editor transpiles TypeScript to JavaScript and runs it (no type checking); see each example's Output for type-check results.
📝 Syntax
interface Bird { fly: () => void }
interface Fish { swim: () => void }
function move(animal: Bird | Fish) {
if ("swim" in animal) {
animal.swim(); // narrowed to Fish
} else {
animal.fly(); // narrowed to Bird
}
}
⚙️ Parameters
| "key" in obj | At runtime, checks whether key is a property of obj (including the prototype chain) and returns a boolean; TS also narrows obj to the union member containing that property |
|---|
Returns:The in expression returns a boolean; narrowing happens at compile time and relies on property exclusivity between union members (having vs lacking a property).
▶ Example
Edit the code below and click Try it to see the transpiled result:
Output:
bubble...
it swims
flap!
it flies
(Runtime output after transpilation; animal.swim() type-checks because in narrowing has fixed animal as Fish — narrowing is guaranteed statically by tsc, at runtime there are only values)
💡 Notes
ℹ️ Info in tests for property existence, including inherited properties on the prototype chain. Plain objects have built-ins like toString and constructor, so check with your own business field names.
⚠️ Note in narrowing relies on exclusivity: if two members both have the same field (differing only in type), in helps little. Prefer a discriminated union with distinct literal values instead.
💡 Tip in is also the core syntax of mapped types ([K in keyof T]), but that is the type-level use; this entry covers the runtime operator plus compile-time narrowing.
🏷️ Related Narrowing & Assertions
💡 Related cases
More cases coming soon.
📚 Related tutorials
❓ FAQ
Q How do I choose between in narrowing and discriminated unions?
A If you can give each member a distinct literal field (type/status/kind), use a discriminated union — clearer and supports exhaustiveness checks. For third-party unions with no discriminant, distinguish structurally with in.
Q Can optional properties mislead in narrowing?
A Yes. An optional flag?: boolean may genuinely be absent, so flag in obj === false does not prove membership in the other branch. Confirm the property is required in one member and absent in the others before relying on in.
Q Does in still narrow correctly after delete removes a property?
A Yes — after delete obj.key the runtime in result is false and TS narrows to the member without that property. But note strict TS already makes deleting a required property a compile error, precisely to keep runtime and declared types in sync.