Optional properties with utility types

Use TypeScript's utility types to set object properties to optional

O ne of TypeScript’s powerful features is its ability to create optional types. Optional types are a way of telling TypeScript that a property does not have to be provided.

Let’s learn how to combine some utility types to help us create a helper type that allows us to specify which properties should be optional.

Creating an optional property

The syntax for creating an optional property is to add a question mark (?) right after the property name.

type Car = {
  brand: string;
  color: string;
  engine?: string;
};

You might be thinking that we could set the engine property type to string or undefined but this is not the case.

TypeScript requires us to explicitly assign a value for engine - even if it is undefined.

type Car = {
  brand: string;
  color: string;
  engine: string | undefined;
};
 
const car: Car = {
  brand: "Ford",
  color: "blue",
};
 
// ❌ Property 'engine' is missing in type '{ brand: string; color: string; }' but required in type 'Car'
 
const validCar: Car = {
  brand: "Ford",
  color: "blue",
  engine: undefined,
};

Partial

We have access to a utility type provided by TypeScript named Partial. Partial sets all of an object types’s properties to optional.

type Car = {
  brand: string;
  color: string;
  engine: string;
};
 
type CarPartial = Partial<Car>;
 
const validCar: CarPartial = {
  brand: "Ford",
};

Pick

TypeScript also provides a utility type called Pick that allows us to create a new type by picking the properties we want from an existing type. A union of properties can be provided to pick multiple properties.

type Car = {
  brand: string;
  color: string;
  engine: string;
};
 
type BrandAndColor = Pick<Car, "brand" | "color">;
// From type Car, create me a new type containing only 'brand' and 'color'.
 
const pickedCar: BrandAndColor = {
  brand: "Ford",
  color: "blue",
};

Omit

Omit is another utility type that allows us to create a new type by excluding the properties we want from an existing type.

Exactly like Pick, we can provide a union to omit multiple properties.

type Car = {
  brand: string;
  color: string;
  engine: string;
};
 
type EngineOnly = Omit<Car, "brand" | "color">;
// From type Car, create me a new type omitting 'brand' and 'color'.
 
const engineOnlyCar: EngineOnly = {
  engine: "V8",
};

Utility types, Assemble!

Let’s stick with our Car type and imagine we want to make a new type that sets only engine to be optional. With our knowledge of how the three utilites, Partial, Pick, and Omit work, we can create our new type.

Think of this as three steps:

  1. Create a new object type, picking the properties we want to make optional in our end result, using Pick.

  2. Use Partial to set all of the properties in the new object type to optional.

  3. Use Omit to create a new object type excluding the properties we want to make optional, using an intersection type to join it all together.

type Car = {
  brand: string;
  color: string;
  engine: string;
};
 
type MakePropertyOptional<Obj, Prop extends keyof Obj> = Partial<
  Pick<Obj, Prop>
> &
  Omit<Obj, Prop>;
 
type OptionalEngine = MakePropertyOptional<Car, "engine">;
 
const car: OptionalEngine = {
  brand: "Ford",
  color: "blue",
};

Improvements

If you hover over the OptionalEngine type in your IDE, it isn’t very pretty or intuitive.

For an improved developer experience, we can create a Prettify helper type to make it easier to decipher.

A tooltip example of the type output in an IDE
type Prettify<T> = {
  [K in keyof T]: T[K];
} & {};
 
type Result = Prettify<OptionalEngine>;
 
/** ✨
 * {
 *   brand: string;
 *   color: string;
 *   engine?: string | undefined;
 * }
 */

It works with unions too!

The great thing about Partial, Omit and Pick is that they work with union types which means our MakePropertyOptional helper works with them too!

type Car = {
  brand: string;
  color: string;
  engine: string;
};
 
type MakePropertyOptional<Obj, Prop extends keyof Obj> = Partial<
  Pick<Obj, Prop>
> &
  Omit<Obj, Prop>;
 
type OptionalEngineAndColor = MakePropertyOptional<Car, "engine" | "color">
 
type Result = Prettify<OptionalEngineAndColor>;
 
/** ✨
 * {
 *   brand: string;
 *   color?: string | undefined;
 *   engine?: string | undefined;
 * }
 */