You’re seeing TS2559 because TypeScript can’t figure out how to merge the types you’re giving it. It’s like trying to combine two Lego bricks that don’t have matching studs or sockets.

This usually happens when you’re trying to use a generic type parameter in a way that makes its constraints too broad, or when you’re trying to spread an object into another where the types are fundamentally incompatible.

Here’s the breakdown of what’s likely going on and how to fix it:

1. Overly Broad Generic Constraints

You’ve probably defined a generic function or class with a type parameter that’s constrained to something like object or any, and then you’re trying to access specific properties on it. TypeScript can’t guarantee those properties exist.

  • Diagnosis: Look at your generic type parameter’s constraint. Is it something like <T extends object> or <T extends any[]> or even just <T> (which defaults to any)? Then, examine where you’re trying to use specific property access or spread syntax on T.
  • Common Cause: You have a function like:
    function mergeObjects<T, U>(obj1: T, obj2: U): T & U {
      return { ...obj1, ...obj2 }; // TS2559 here if T or U are too broad
    }
    
    If T or U could be anything, ...obj1 or ...obj2 might be trying to spread a primitive or null/undefined.
  • Fix: Be more specific with your constraints. If you expect objects with certain properties, define an interface or type for them.
    interface HasName {
      name: string;
    }
    
    function mergeObjects<T extends object, U extends object>(obj1: T, obj2: U): T & U {
      return { ...obj1, ...obj2 };
    }
    
    Or, if you know the specific shape:
    function mergeObjects<T extends { id: number }, U extends { value: string }>(obj1: T, obj2: U): T & U {
      return { ...obj1, ...obj2 };
    }
    
  • Why it works: By adding a constraint like extends { id: number }, you tell TypeScript that T must have an id property of type number. This makes the spread operation safe because TypeScript knows the properties exist.

2. Spreading null or undefined

You might be conditionally assigning values, and one of the branches results in null or undefined being spread.

  • Diagnosis: Trace the origin of the objects being spread. Are they coming from optional properties, function return values that can be null, or conditional logic?
  • Common Cause:
    interface User {
      id: number;
      profile?: { bio: string };
    }
    
    function getUserBio(user: User): string | undefined {
      const profileData = user.profile; // profileData could be undefined
      return { ...profileData }.bio; // TS2559 here
    }
    
  • Fix: Add checks for null or undefined before spreading, or use optional chaining and nullish coalescing.
    function getUserBio(user: User): string | undefined {
      const profileData = user.profile;
      if (!profileData) {
        return undefined;
      }
      return profileData.bio; // Direct access is safe after the check
    }
    
    Or, if you really want to spread (though direct access is cleaner):
    function getUserBio(user: User): string | undefined {
      const profileData = user.profile;
      // Spread only if profileData is not null or undefined
      return profileData ? { ...profileData }.bio : undefined;
    }
    
  • Why it works: The spread operator (...) expects an object. Spreading null or undefined is invalid because they don’t have properties to spread. Explicitly checking and handling these cases ensures you only spread valid objects.

3. Mismatched Union Types in Spread

You’re trying to merge two types that are unions, and there are no common properties across all members of the union.

  • Diagnosis: Examine the types involved in the spread. If they are unions (e.g., TypeA | TypeB), check if TypeA has properties that TypeB lacks, and vice-versa.
  • Common Cause:
    type Circle = { radius: number; color: string };
    type Square = { sideLength: number; color: string };
    
    function drawShape(shape: Circle | Square): void {
      const { color, ...rest } = shape; // TS2559 if rest is used and properties differ
      console.log(`Color: ${color}`);
      // If you tried to spread `rest` into something expecting specific properties, TS2559
    }
    
    If you then tried to do something like const data = { ...rest, originalShape: shape }; it would fail.
  • Fix: Use type guards to narrow down the union before attempting to access or spread properties that are not common to all members.
    function drawShape(shape: Circle | Square): void {
      const { color, ...rest } = shape;
      console.log(`Color: ${color}`);
    
      if ('radius' in shape) {
        // Here, shape is narrowed to Circle
        const circleData = { ...rest, radius: shape.radius };
        console.log("Circle specific:", circleData);
      } else {
        // Here, shape is narrowed to Square
        const squareData = { ...rest, sideLength: shape.sideLength };
        console.log("Square specific:", squareData);
      }
    }
    
  • Why it works: Type guards ('radius' in shape) allow TypeScript to understand which specific type within the union you’re dealing with. This enables safe access to properties unique to that type and prevents trying to spread a "partial" object into a context that expects a full, specific shape.

4. Spreading Non-Object Types into Object Types

You’re attempting to spread a primitive (like a number, string, boolean) or a function into an object type.

  • Diagnosis: Check the type of the value you are spreading. Is it a primitive type, null, undefined, or a function?
  • Common Cause:
    function processValue(val: number | { data: string }): void {
      const result = { ...val, processed: true }; // TS2559 if val is 'number'
    }
    
  • Fix: Ensure that the value being spread is always an object type. Use type guards or conditional logic to handle non-object types separately.
    function processValue(val: number | { data: string }): void {
      let result: object; // Or a more specific object type
      if (typeof val === 'number') {
        result = { originalValue: val, processed: true };
      } else {
        result = { ...val, processed: true };
      }
      console.log(result);
    }
    
  • Why it works: The spread syntax is designed to copy properties from one object to another. It has no mechanism to extract or use properties from non-object types, hence the error.

5. Incorrect Use of Partial or Required with Spreads

While Partial and Required are useful, they can sometimes mask underlying issues if not used carefully with spread syntax.

  • Diagnosis: If you’re using Partial<T> or Required<T> in conjunction with spread syntax, ensure that the original type T actually has the properties you expect to spread, even if they are optional.
  • Common Cause:
    interface Config {
      timeout?: number;
      retries: number;
    }
    
    function updateConfig(newConfig: Partial<Config>): Config {
      const defaultConfig: Config = { timeout: 5000, retries: 3 };
      return { ...defaultConfig, ...newConfig }; // TS2559 if newConfig is unexpectedly empty or has weird types
    }
    
    This specific example is usually fine because Partial<Config> can be an empty object, and merging defaultConfig with an empty object is valid. The error comes if newConfig itself has a type that cannot be spread, e.g., if it was null or undefined due to prior logic.
  • Fix: Ensure that the source of your Partial or Required type is correctly typed and that any values being spread are not null or undefined unless explicitly handled.
    function updateConfig(newConfig: Partial<Config> | null | undefined): Config {
      const defaultConfig: Config = { timeout: 5000, retries: 3 };
      const effectiveNewConfig = newConfig ?? {}; // Use nullish coalescing to provide a default empty object
      return { ...defaultConfig, ...effectiveNewConfig };
    }
    
  • Why it works: Partial<T> creates a type where all properties of T are optional. When spreading Partial<T>, TypeScript knows it’s dealing with an object structure, even if all properties might be missing. The error arises if the actual value passed is not an object (like null or undefined), which the nullish coalescing operator ?? {} handles.

The next error you’ll likely encounter after fixing TS2559 is a type mismatch in the resulting object, as you’ve now successfully merged types that might still have incompatible property types for the same key.

Want structured learning?

Take the full Typescript course →