Headshot of Marius Schulz
Marius Schulz Front End Engineer

TypeScript 2.1: Object Rest and Spread

TypeScript 2.1 adds support for the Object Rest and Spread Properties proposal that is slated for standardization in ES2018. You can work with rest and spread properties in a type-safe manner and have the compiler downlevel both features all the way down to ES3.

Object Rest Properties

Let's assume you have defined a simple object literal with three properties:

const marius = {
  name: "Marius Schulz",
  website: "https://mariusschulz.com/",
  twitterHandle: "@mariusschulz"

Using the ES2015 destructuring syntax, you can create several local variables that hold the values of the corresponding property. TypeScript will correctly infer the type of each variable:

const { name, website, twitterHandle } = marius;

name;          // Type string
website;       // Type string
twitterHandle; // Type string

That's all good and true, but nothing new so far. This is where object rest comes into play and enables another destructuring feature: In addition to extracting a set of properties you're interested in, you can collect all remaining properties in a rest element using the ... syntax:

const { twitterHandle, ...rest } = marius;

twitterHandle; // Type string
rest; // Type { name: string; website: string; }

TypeScript will determine the correct types for all resulting local variables. While the twitterHandle variable is a plain string, the rest variable is an object containing the remaining two properties which weren't destructured separately.

Object Spread Properties

Let's assume you want to use the fetch() API to make an HTTP request. It accepts two parameters: a URL and an options object containing any custom settings that you want to apply to the request.

In your application, you might encapsulate the call to fetch() and provide default options and the possibility to override specific settings for a given request. These options objects can look like this:

const defaultOptions = {
  method: "GET",
  credentials: "same-origin"

const requestOptions = {
  method: "POST",
  redirect: "follow"

Using object spread, you can merge both objects into a single new object that you can the pass to the fetch() method:

// Type { method: string; redirect: string; credentials: string; }
const options = {

Object spread will create a new object, copy over all property values from defaultOptions, and then copy over all property values from requestOptions — in that order, from left to right. Here's the result:

// {
//   method: "POST",
//   credentials: "same-origin",
//   redirect: "follow"
// }

Notice that the order of assignments matters! If a property appears in both objects, the later assignment wins. This is why defaultOptions is listed before requestOptions — if it was the other way around, there would be no way to override the defaults.

Of course, TypeScript understands this ordering. Therefore, if multiple spread objects define a property with the same key, the type of that property in the resulting object will be the type of the property of the last assignment because it overrides previously assigned values of that property:

const obj1 = { prop: 42 };
const obj2 = { prop: "Hello World" };

const result1 = { ...obj1, ...obj2 }; // Type { prop: string }
const result2 = { ...obj2, ...obj1 }; // Type { prop: number }

In a nutshell: later assignments win.

Making Shallow Copies of Objects

Object spread can be used to create a shallow copy of an object. Let's say you want to create a new todo item from an existing one by creating a new object and copying over all properties. With object spread, that's a one-liner:

const todo = {
  text: "Water the flowers",
  completed: false,
  tags: ["garden"]

const shallowCopy = { ...todo };

And indeed, you get a new object with all property values copied:

console.log(todo === shallowCopy);
// false

// {
//   text: "Water the flowers",
//   completed: false,
//   tags: ["garden"]
// }

You can now modify the text property without changing the original todo item:

shallowCopy.text = "Mow the lawn";

// {
//   text: "Mow the lawn",
//   completed: false,
//   tags: ["garden"]
// }

// {
//   text: "Water the flowers",
//   completed: false,
//   tags: ["garden"]
// }

However, the new todo item references the same tags array as the first one. No deep clone was made! Therefore, mutating the array will impact both todos:


// {
//   text: "Mow the lawn",
//   completed: false,
//   tags: ["garden", "weekend"]
// }

// {
//   text: "Water the flowers",
//   completed: false,
//   tags: ["garden", "weekend"]
// }

If you want to create a deep clone of a serializable object, consider JSON.parse(JSON.stringify(obj)) or some other approach. Just like Object.assign(), object spread only copies over property values, which might lead to unintended behavior if a value is a reference to another object.

Note that none of the code snippets in this post contain any type annotations or other TypeScript-specific constructs. It's just plain JavaScript mixed with the proposed object rest syntax. Type inference for the win!

This post is part of the TypeScript Evolution series:

  1. TypeScript 2.0: Non-Nullable Types
  2. TypeScript 2.0: Control Flow Based Type Analysis
  3. TypeScript 2.0: Acquiring Type Declaration Files
  4. TypeScript 2.0: Read-Only Properties
  5. TypeScript 2.0: Tagged Union Types
  6. TypeScript 2.0: More Literal Types
  7. TypeScript 2.0: The never Type
  8. TypeScript 2.0: Built-In Type Declarations
  9. TypeScript 2.1: async/await for ES3/ES5
  10. TypeScript 2.1: External Helpers Library
  11. TypeScript 2.1: Object Rest and Spread
  12. TypeScript 2.1: keyof and Lookup Types
  13. TypeScript 2.1: Mapped Types
  14. TypeScript 2.1: Improved Inference for Literal Types
  15. TypeScript 2.1: Literal Type Widening
  16. TypeScript 2.1: Untyped Imports
  17. TypeScript 2.2: The object Type
  18. TypeScript 2.2: Dotted Properties and String Index Signatures
  19. TypeScript 2.2: Null-Checking for Expression Operands
  20. TypeScript 2.2: Mixin Classes
  21. TypeScript 2.3: Generic Parameter Defaults
  22. TypeScript 2.3: The --strict Compiler Option
  23. TypeScript 2.3: Type-Checking JavaScript Files with --checkJs
  24. TypeScript 2.3: Downlevel Iteration for ES3/ES5
  25. TypeScript 2.4: String Enums
  26. TypeScript 2.4: Weak Type Detection
  27. TypeScript 2.4: Spelling Correction
  28. TypeScript 2.4: Dynamic import() Expressions
  29. TypeScript 2.5: Optional catch Binding
  30. TypeScript 2.6: JSX Fragment Syntax
  31. TypeScript 2.7: Numeric Separators
  32. TypeScript 2.7: Strict Property Initialization
  33. TypeScript 2.8: Per-File JSX Factories
  34. TypeScript 2.8: Conditional Types
  35. TypeScript 2.8: Mapped Type Modifiers