The spread operator (the three dots of JavaScript) and its uses

OpenJavaScript 0
Reading Time: 6 minutes πŸ•‘

Last updated: February 9, 2022.

You may have noticed the appearance of three little dots (...) in some JavaScript code and what it means. This is the spread operator and is used for ‘spreading’ the individual elements of an iterable:

const myArray = [4, 6, 3, 5, 3, 5];

console.log(myArray);  
// Prints the array object:
// [4,6,3,5,3,5]

console.log(...myArray);
// Prints the individual elements of the array:
// 4 6 3 5 3 5

This may not seem like such a big deal, but there are a whole load of use cases where these three little dots prove extremely useful.

In this article, we walk you through the various use cases for the spread operator.

Uses of the spread operator

#1: Copy a simple array

By spreading the individual elements of an array into a new array, the spread operator provides a fast and clean way to make an array copy:

const myArray = [4, 6, 3, 5, 3, 5];

const arrayCopy = [...myArray];

console.log(arrayCopy)
// [4, 6, 3, 5, 3, 5]

You might intuitively think that there is a simpler way to do this: set the value of arrayCopy to myArray. But in JavaScript, this only creates a reference to the original array. Thus, if the copy is edited, the original is also edited:

const myArray = [4, 6, 3, 5, 3, 5];

const arrayCopy = myArray;

arrayCopy[0] = "🐢";

console.log(arrayCopy); // βœ”οΈ
// ["🐢",6,3,5,3,5]
console.log(myArray); // 😱
// ["🐢",6,3,5,3,5]

The behavior in the above code is almost always unwanted. The spread operator helps us to make a better array copy:

const myArray = [4, 6, 3, 5, 3, 5];

const arrayCopy = [...myArray];

arrayCopy[0] = "🐢";

console.log(arrayCopy); // βœ”οΈ
// ["🐢",6,3,5,3,5]
console.log(myArray); // βœ”οΈ
// [4,6,3,5,3,5]

But if your array contains nested properties, the referencing problem emerges for the nested properties even when using the spread operator:

const myArray = [4, 6, 3, 5, [3, 5]];
    
const arrayCopy = [...myArray];
    
arrayCopy[4][1] = "🐢";

console.log(arrayCopy); // βœ”οΈ
// ["🐢",6,3,5,3,5]
console.log(myArray); // 😱
// ["🐢",6,3,5,3,5]

To make a copy of an array that contains nesting, we recommend using the .cloneDeep method from the third-party Lodash library.

The following code works if Lodash is already loaded from a CDN:

// Lodash already loaded in earlier script tag

const myArray = [4, 6, 3, 5, [3, 5]];
    
const arrayCopy = _.cloneDeep(myArray);
    
arrayCopy[4][1] = "🐢";

console.log(arrayCopy); // βœ”οΈ
// [4, 6, 3, 5, [3, "🐢"]]
console.log(myArray); // βœ”οΈ
// [4, 6, 3, 5, [3, 5]]

#2: Merge arrays

Existing arrays can be merged into a new one by spreading their individual elements into a new array:

const dogArray = ["🐢", "🐢", "🐢"];
const catArray = ["🐱", "🐱", "🐱"];

const animalArray = [...dogArray, ...catArray];

console.log(animalArray);
// ["🐢","🐢","🐢","🐱","🐱","🐱"]

The spread operator can also be used in combination with normally specified array elements:

const catArray = ["🐱", "🐱", "🐱"];

const catArray2 = [...catArray, "Kitty", "Cat", "Meow"];

console.log(catArray2);
// ["🐱","🐱","🐱","Kitty","Cat","Meow"]

For completeness, here's what would happen if we didn't use the spread operator in the first example:

const dogArray = ["🐢", "🐢", "🐢"];
const catArray = ["🐱", "🐱", "🐱"];

const animalArray = [dogArray, catArray];

console.log(animalArray);
// [["🐢","🐢","🐢"],["🐱","🐱","🐱"]]

We end up with an array containing two array objects: dogArray and catArray.

#3: With Math.min() and Math.max()

The .min() and .max() methods available on the Math object accept a list of numbers and returns the lowest or largest number from the list. Try passing in an array object and NaN will be returned because although it contains a list of numbers, it is itself an object.

The spread operator provides a simple way to get around this problem by spreading array values into Math.min() and Math.max():

const myNumberArray = [5,2,7,4,4,2,7,8,2,3,5];

const minValue = Math.min(...myNumberArray);
// Is the same as: Math.min(5,2,7,4,4,2,7,8,2,3,5)

const maxValue = Math.max(...myNumberArray);
// Is the same as: Math.max(5,2,7,4,4,2,7,8,2,3,5)

console.log(minValue); // 2
console.log(maxValue); // 8

And here's the wrong way to do it:

// ❌ Math.min or Math.max do not work if an array object is passed in:
const myNumberArray = [5,2,7,4,4,2,7,8,2,3,5];

const minValue = Math.min(myNumberArray);
const maxValue = Math.max(myNumberArray);

console.log(minValue); // NaN
console.log(maxValue); // NaN

#4: Create a function with an unknown number of arguments

A function accepts parameters as a comma-separated list.

By using the spread operator, we can create a function with an unspecified number of arguments passed in when called:

function(...args) {
  // function statement
}

When is this useful? For example, when we want to join a list of string values together inside a function, but are unsure how many will be passed in each time:

function printFullName (...args) {
  const fullName = args.join(' ');
  console.log(fullName);
}

printFullName("Kiefer", "William", "Frederick", "Dempsey", "George", "Rufus", "Sutherland");
// "Kiefer William Frederick Dempsey George Rufus Sutherland"
printFullName("BeyoncΓ©");
// "BeyoncΓ©"

#5: List characters of a string

Just as the spread operator can be used to list array elements, it can also list the characters of a string individually:

const name = "Britney";

console.log(...name);
// "B" "r" "i" "t" "n" "e" "y"

#6: Get non-destructured elements in array and object destructuring

When destructuring an array or object, specifying ...rest saves non-destructured elements to a new array or object called rest.

For example, here rest becomes all elements apart from the destructured "Batman":

const superheroes = ["πŸ¦‡", "πŸ•ΈοΈ", "🧲"];
 
const [hero1, ...rest] = superheroes;

console.log(hero1);
// "πŸ¦‡"

console.log(rest);
// ["πŸ•ΈοΈ", "🧲"];

And in object destructuring, it works in the same way: a new object is produced, containing in the below example the non-destructured thumbnail and story properties:

const newsStory = {
   title: "Spiderman: Terrorist or hero?",
   category: "πŸ•ΈοΈ",
   printTitle: function() {
     "The title of this article is "+this.title;
   }
}
 
const { title, ...rest } = newsStory;
 
console.log(title);
"Spiderman: Terrorist or hero?"

console.log(rest);
// {
//    category: "πŸ•ΈοΈ",
//    printTitle: function() {
//      "The title of this article is "+this.title;
//    }
// }

For more, you may want to check out our full tutorial on object and array destructuring.

#7: Shallow copy an object

In the ES2018 update to JavaScript, the use of the spread operator was extended to objects.

This allows copying an object by spreading the contents of an object into a new one:

const newsStory = {
   title: "Spiderman: Terrorist or hero?",
   category: "πŸ•ΈοΈ",
   printTitle: function() {
     "The title of this article is "+this.title;
   }
}

const dataCopy = {...newsStory};
dataCopy.category = "πŸ“°"

console.log(dataCopy); βœ”οΈ
// {
//   title: "Spiderman: Terrorist or hero?",
//   category: "πŸ“°",
//    printTitle: function() {
//     "The title of this article is "+this.title;
//    }
// }

console.log(newsStory); βœ”οΈ
// {
//   title: "Spiderman: Terrorist or hero?",
//   category: "πŸ•ΈοΈ",
//    printTitle: function() {
//     "The title of this article is "+this.title;
//    }
// }

This works perfectly well for copying non-nested properties, including those that contain methods.

But any nested properties in the copy are only references to the original object.

This means that if nested properties in the copy are changed, so too is the original object:

const newsStory = {
   title: "Spiderman: Terrorist or hero?",
   category: "πŸ•ΈοΈ",
   attributes: {
     author: "Mary Jane Watson",
     importance: "πŸ”΄",
   }
}

const dataCopy = {...newsStory};
dataCopy.attributes.importance = "🟑";

console.log(dataCopy); βœ”οΈ

// {
//   title: "Spiderman: Terrorist or hero?",
//   category: "πŸ•ΈοΈ",
//   attributes: {
//     author: "Mary Jane Watson",
//     importance: "🟑",
//   }
// }

console.log(newsStory); 😨

// {
//    title: "Spiderman: Terrorist or hero?",
//    category: "πŸ•ΈοΈ",
//    attributes: {
//      author: "Mary Jane Watson",
//      importance: "🟑",
//    }
// }

To make a deep copy of an object, including its nested properties, we recommend using the .cloneDeep method from the third-party Lodash library. For setup instructions, check out our full article on object cloning.

#8: Merge objects

The properties of two or more objects can be combined by spreading their contents into a new object:

const breakfastMenu =  {
  croissant: "πŸ₯",
  bagel: "πŸ₯―"
}

const lunchMenu = {
  spagBowl: "🍝",
  steamedRice: "🍚",
}

const fullMenu = {...breakfastMenu, ...lunchMenu};
console.log(fullMenu); //βœ”οΈAll properties are now in new object
// {
//  croissant: "πŸ₯",
//  bagel: "πŸ₯―"
//  spagBowl: "🍝",
//  steamedRice: "🍚",
// }

Make sure that properties do not share the same key values when doing this, because otherwise properties spread earlier will be overwritten:

// ❌ The wrong way wish clashing property keys:

const breakfastMenu =  {
  option1: "πŸ₯",
  option2: "πŸ₯―"
}

const lunchMenu = {
  option1: "🍝",
  option2: "🍚",
}

const fullMenu = {...breakfastMenu, ...lunchMenu};
console.log(fullMenu);
// {
//  spagBowl: "🍝",
//  steamedRice: "🍚",
// }

Summary

The spread operator performs a simple operation: listing the contents of an iterable in a comma-separated list. Due to its popularity, its use was extended to non-iterable objects in ES2018.

As demonstrated by the examples in this article, the spread operator has many and varied use cases that avoid the need for complicated workarounds.

Though incredibly useful, it is important to remember that it is not appropriate for copying arrays or objects with nesting. For this, we recommend using .deepClone from the Lodash library.