How to fix Operator 'in' cannot be applied to types 'X' and 'Y' in TypeScript

TypeScriptINTERMEDIATEMEDIUMUpdated Dec 11, 2025· 110 views

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.

Quick fix

Fixes TS2361

// 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);
}
  1. Ensure you're checking property existence in objects
  2. Use proper type guards instead of 'in' for type checking
  3. Constrain generic types to object when using 'in'
  4. Handle union types containing both objects and primitives
typescript — error
// 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);
}
Operator 'in' cannot be applied to types 'X' and 'Y'

What this error means

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

Typical symptoms

  • TypeScript compilation fails with error TS2361
  • IDE shows red underlines on 'in' operator usage
  • Error message: 'Operator 'in' cannot be applied to types 'string' and 'T''
  • Code works in JavaScript but fails TypeScript type checking
  • Error appears when using 'in' with generic types or union types
  • Trying to use 'in' to check if a value is of a certain type (incorrect usage)

Common causes

  1. Using 'in' operator with primitive types (string, number, boolean, etc.) instead of objects
  2. Attempting to check type membership with 'in' instead of proper type guards
  3. Union types containing both object and non-object types
  4. Generic type parameters without proper constraints (not extending object)
  5. Confusing JavaScript's 'in' operator with TypeScript's type narrowing features
  6. Trying to use 'in' to check if a string is in an array (should use includes() instead)
  7. Incorrectly assuming 'in' works like Python's 'in' operator for collections

How to fix "Operator 'in' cannot be applied to types 'X' and 'Y'"

1Ensure you're checking property existence in objects

The "in" operator should only be used to check if a property exists in an object:

typescript
// 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);
}
2Use proper type guards instead of 'in' for type checking

If you're trying to check if a value is of a certain type, use type guards, not the "in" operator:

typescript
// 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
  }
}
3Constrain generic types to object when using 'in'

When using "in" with generic types, ensure the type parameter extends object:

typescript
// 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;
}
4Handle union types containing both objects and primitives

When working with union types, you may need to narrow the type before using "in":

typescript
// 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);
    }
  });
}
5Use correct methods for array/collection membership checks

Don't confuse JavaScript's "in" operator with collection membership checks:

typescript
// 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();
}
6Refactor code to avoid 'in' operator when inappropriate

Sometimes the best fix is to redesign your approach:

typescript
// 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);
}

Advanced notes

### Understanding the 'in' Operator's TypeScript Behavior

TypeScript's "in" operator narrowing is a powerful feature that works differently from JavaScript:

typescript
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'

typescript
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":

typescript
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

typescript
// '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

Frequently asked questions

What does "TS2361" mean?
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.
How do I fix "TS2361"?
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.
What causes "TS2361"?
Using 'in' operator with primitive types (string, number, boolean, etc.) instead of objects Attempting to check type membership with 'in' instead of proper type guards Union types containing both object and non-object types
Is the "TS2361" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading