This TypeScript error occurs when you try to use the "in" operator with types that don't support it. The "in" operator is specifically for checking property existence in objects, not for general type comparisons. The fix involves ensuring you're using "in" with object types or using proper type guards.
The TypeScript error "Operator 'in' cannot be applied to types 'X' and 'Y'" occurs when you attempt to use the JavaScript "in" operator with incompatible types. The "in" operator is designed specifically to check if a property exists in an object, but TypeScript is detecting that you're trying to use it with types that aren't valid for this operation. In JavaScript, the "in" operator returns true if the specified property is in the specified object or its prototype chain. However, TypeScript adds type safety by ensuring both sides of the "in" operator are compatible: - The left operand must be a string, number, or symbol (representing a property name) - The right operand must be an object type (not a primitive like string, number, boolean, etc.) This error commonly appears when: 1. Trying to use "in" with primitive types (string, number, boolean) 2. Using "in" with union types where some members aren't objects 3. Attempting to check type membership incorrectly (confusing "in" with "instanceof" or type guards) 4. Using "in" with generic types that haven't been properly constrained
The "in" operator should only be used to check if a property exists in an object:
// CORRECT: Checking property in object
interface User {
name: string;
age?: number; // Optional property
}
const user: User = { name: "Alice" };
if ("age" in user) {
console.log(user.age); // TypeScript knows age exists here
}
// CORRECT: Checking index signature
interface Dictionary {
[key: string]: number;
}
const dict: Dictionary = { "a": 1, "b": 2 };
if ("c" in dict) {
console.log(dict.c); // TypeScript knows c exists
}
// WRONG: Using 'in' with primitives
const str = "hello";
if ("length" in str) { // Error: 'in' can't be used with string primitive
console.log(str.length);
}
// CORRECT: Use typeof or other methods for primitives
if (typeof str === "string") {
console.log(str.length);
}If you're trying to check if a value is of a certain type, use type guards, not the "in" operator:
// WRONG: Trying to check type with 'in'
function processValue(value: string | number) {
if (value in string) { // Error: 'in' can't be used this way
console.log(value.toUpperCase());
}
}
// CORRECT: Use typeof for primitive types
function processValue(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // TypeScript knows it's string
} else {
console.log(value.toFixed(2)); // TypeScript knows it's number
}
}
// CORRECT: Use instanceof for class instances
class Animal {
move() { console.log("Moving"); }
}
class Bird extends Animal {
fly() { console.log("Flying"); }
}
function handleAnimal(animal: Animal) {
if (animal instanceof Bird) {
animal.fly(); // TypeScript knows it's Bird
} else {
animal.move();
}
}
// CORRECT: User-defined type guards
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isCat(pet: Cat | Dog): pet is Cat {
return (pet as Cat).meow !== undefined;
}
function handlePet(pet: Cat | Dog) {
if (isCat(pet)) {
pet.meow(); // TypeScript knows it's Cat
} else {
pet.bark(); // TypeScript knows it's Dog
}
}When using "in" with generic types, ensure the type parameter extends object:
// WRONG: Generic without constraint
function hasProperty<T>(obj: T, key: string): boolean {
return key in obj; // Error: 'in' can't be applied to types 'string' and 'T'
}
// CORRECT: Constrain T to extend object
function hasProperty<T extends object>(obj: T, key: string): boolean {
return key in obj; // OK: T is guaranteed to be an object
}
// CORRECT: More specific constraint
function hasProperty<T extends Record<string, any>>(obj: T, key: string): boolean {
return key in obj;
}
// Usage examples
const user = { name: "Alice", age: 30 };
console.log(hasProperty(user, "name")); // true
console.log(hasProperty(user, "email")); // false
// WRONG: Can't use with primitives
const str = "hello";
// console.log(hasProperty(str, "length")); // Error: string doesn't extend object
// For checking properties that might be on primitives, use a different approach
function hasPropertyOrPrimitive<T>(value: T, key: string): boolean {
if (typeof value === "object" && value !== null) {
return key in value;
}
return false;
}When working with union types, you may need to narrow the type before using "in":
// Problem: Union type with object and primitive
type Response = string | { data: any; error?: string };
function handleResponse(response: Response) {
// WRONG: Can't use 'in' directly
// if ("data" in response) { ... }
// CORRECT: Check type first
if (typeof response === "object" && response !== null) {
// Now TypeScript knows response is object type
if ("data" in response) {
console.log(response.data);
}
if ("error" in response) {
console.log(response.error);
}
} else {
console.log("String response:", response);
}
}
// Alternative: Discriminated union
type SuccessResponse = { type: "success"; data: any };
type ErrorResponse = { type: "error"; message: string };
type ApiResponse = SuccessResponse | ErrorResponse | string;
function handleApiResponse(response: ApiResponse) {
if (typeof response === "string") {
console.log("Raw string:", response);
} else if (response.type === "success") {
console.log("Success:", response.data); // TypeScript knows it's SuccessResponse
} else {
console.log("Error:", response.message); // TypeScript knows it's ErrorResponse
}
}
// For arrays of mixed types
type MixedArray = (string | { id: number; name: string })[];
function processMixedArray(arr: MixedArray) {
arr.forEach(item => {
if (typeof item === "object") {
// Now safe to use 'in'
if ("name" in item) {
console.log(item.name);
}
} else {
console.log("String item:", item);
}
});
}Don't confuse JavaScript's "in" operator with collection membership checks:
// WRONG: Using 'in' to check if value is in array
const colors = ["red", "green", "blue"];
const color = "red";
if (color in colors) { // This checks array indices, not values!
console.log("Found"); // Actually checks if "red" is a property/index
}
// What the above actually does:
// "0" in colors → true (index 0 exists)
// "red" in colors → false ("red" is not an index)
// "length" in colors → true (arrays have length property)
// CORRECT: Use includes() for arrays
if (colors.includes(color)) {
console.log("Found color in array");
}
// CORRECT: Use has() for Sets
const colorSet = new Set(["red", "green", "blue"]);
if (colorSet.has(color)) {
console.log("Found color in Set");
}
// CORRECT: Use has() for Maps
const colorMap = new Map([["red", "#FF0000"], ["green", "#00FF00"]]);
if (colorMap.has(color)) {
console.log("Found color in Map:", colorMap.get(color));
}
// CORRECT: Use in for object property checking
const colorObj = { red: "#FF0000", green: "#00FF00", blue: "#0000FF" };
if (color in colorObj) {
console.log("Found color in object:", colorObj[color]);
}
// For checking if method exists (less common, usually better approaches)
const obj = { greet: () => "Hello", count: 42 };
if ("greet" in obj && typeof obj.greet === "function") {
obj.greet();
}Sometimes the best fix is to redesign your approach:
// Problematic pattern: Using 'in' for type discrimination
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
// WRONG approach
function getArea(shape: Shape): number {
if ("radius" in shape) { // Works but not ideal
return Math.PI * shape.radius ** 2;
} else {
return shape.sideLength ** 2;
}
}
// BETTER: Discriminated union with kind property
function getAreaBetter(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2; // TypeScript knows it's Circle
} else {
return shape.sideLength ** 2; // TypeScript knows it's Square
}
}
// EVEN BETTER: Switch statement
function getAreaBest(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
}
}
// For dynamic property checking, consider alternative patterns
interface Config {
apiUrl?: string;
timeout?: number;
retries?: number;
}
// Instead of checking each property with 'in'
function validateConfig(config: Config): string[] {
const errors: string[] = [];
// Verbose with 'in'
if (!("apiUrl" in config)) errors.push("apiUrl is required");
if (!("timeout" in config)) errors.push("timeout is required");
return errors;
}
// Better: Use optional chaining and nullish coalescing
function validateConfigBetter(config: Config): string[] {
const errors: string[] = [];
if (!config.apiUrl) errors.push("apiUrl is required");
if (config.timeout == null) errors.push("timeout is required");
return errors;
}
// Best: Schema validation library (Zod, Yup, etc.)
import { z } from "zod";
const ConfigSchema = z.object({
apiUrl: z.string().url(),
timeout: z.number().min(100),
retries: z.number().optional(),
});
function validateConfigBest(config: unknown) {
return ConfigSchema.safeParse(config);
}### Understanding the 'in' Operator's TypeScript Behavior
TypeScript's "in" operator narrowing is a powerful feature that works differently from JavaScript:
interface A {
x: number;
common?: string;
}
interface B {
y: number;
common?: string;
}
function test(value: A | B) {
// TypeScript narrows based on 'in' checks
if ("x" in value) {
// value is A here
console.log(value.x);
// value.common exists (from union)
} else {
// value is B here
console.log(value.y);
// value.common also exists here
}
}Important: The property you check with "in" must be a discriminant - a property that exists in some union members but not others. If all union members have the property (even optionally), TypeScript won't narrow the type.
### 'in' vs 'hasOwnProperty' vs 'property in object'
const obj = { a: 1 };
const proto = { b: 2 };
Object.setPrototypeOf(obj, proto);
console.log("a" in obj); // true (own property)
console.log("b" in obj); // true (inherited property)
console.log(obj.hasOwnProperty("a")); // true
console.log(obj.hasOwnProperty("b")); // false
// TypeScript understands hasOwnProperty narrowing
function process(value: { a: number } | { b: number }) {
if (value.hasOwnProperty("a")) {
// TypeScript knows value has 'a'
console.log(value.a);
} else {
// TypeScript knows value has 'b'
console.log(value.b);
}
}### Performance Considerations
- "in" operator: Checks prototype chain, can be slower for deep inheritance
- hasOwnProperty(): Only checks own properties, generally faster
- Optional chaining (?.): Modern, clean, but different semantics
### Type Predicates with 'in'
You can create reusable type guards using "in":
interface Admin {
adminId: string;
permissions: string[];
}
interface User {
userId: string;
email: string;
}
function isAdmin(user: Admin | User): user is Admin {
return "adminId" in user;
}
// Usage
function handleUser(user: Admin | User) {
if (isAdmin(user)) {
// TypeScript knows user is Admin
console.log(user.permissions);
} else {
// TypeScript knows user is User
console.log(user.email);
}
}### Edge Cases with null and undefined
// 'in' operator with null/undefined
const val: unknown = null;
if (val !== null && val !== undefined && "property" in val) {
// TypeScript knows val is object here
console.log(val.property);
}
// Optional chaining alternative
if (val?.property !== undefined) {
// Works but different semantics
}### Historical Context
The "in" operator narrowing was significantly improved in TypeScript 4.9 (November 2022). Before that, TypeScript was less precise about narrowing union types with the "in" operator. If you're seeing different behavior, check your TypeScript version.
### When to Use Alternatives
Consider these alternatives to "in":
1. Discriminated unions: Best for fixed set of known types
2. Type predicates: Reusable, explicit type guards
3. Type assertions: When you know more than TypeScript (use sparingly)
4. Schema validation: For runtime type checking of external data
5. Branded types: For nominal typing patterns
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