HomepageExploring TypeScript (TS 5.8 Edition)
You can support this book: buy it or donate
(Ad, please don’t block.)

20 Intersections of object types

In this chapter, we explore what intersections of object types can be used for in TypeScript.

In this chapter, object type means:

20.1 Intersections of object types

The intersection of two object types has the properties of both:

type Obj1 = { prop1: boolean };
type Obj2 = { prop2: number };

const obj: Obj1 & Obj2 = {
  prop1: true,
  prop2: 123,
};

20.1.1 Extending vs. intersection

With interfaces, we can use extends to add properties:

interface Person {
  name: string;
}
interface Employe extends Person {
  company: string;
}

With object types, we can use an intersection:

type Person = {
  name: string,
};

type Employee =
  & Person
  & {
    company: string,
  }
;

One caveat is that only extends supports overriding. For more information, see $type.

20.1.2 Example: NonNullable (T & {})

/**
 * Exclude null and undefined from T
 */
type NonNullable<T> = T & {};

type _ = [
  Assert<Equal<
    NonNullable<undefined | string>,
    string
  >>,
  Assert<Equal<
    NonNullable<null | string>,
    string
  >>,
  Assert<Equal<
    NonNullable<string>, // (A)
    string
  >>,
];

The result of NonNullable<T> is a type that is the intersection of T and all non-nullish values.

It’s interesting that string & {} is string (line A).

20.1.3 Example: inferred intersections

The following code shows how the inferred type of obj changes when we use the built-in type guard in (line A and line B):

function func(obj: object) {
  if ('prop1' in obj) { // (A)
    assertType<
      object & Record<'prop1', unknown>
    >(obj);
    if ('prop2' in obj) { // (B)
      assertType<
      object & Record<'prop1', unknown> & Record<'prop2', unknown>
      >(obj);
    }
  }
}

20.1.4 Example: combining two object types via an intersection

In the next example, we combine the type Obj of a parameter with the type WithKey – by adding the property .key of WithKey to the parameter:

type WithKey = {
  key: string,
};
function addKey<Obj extends object>(obj: Obj, key: string)
  : Obj & WithKey
{
  const objWithKey = obj as (Obj & WithKey);
  objWithKey.key = key;
  return objWithKey;
}

addKey() is used like this:

const paris = {
  city: 'Paris',
};

const parisWithKey = addKey(paris, 'paris');
assertType<
{ 
  city: string,
  key: string,
}
>(parisWithKey);