Spread and Rest operator

ES6 version came out with some new features, ...(triple dot) is one of those new JS feature.

It can be used in two different ways, as a -

  • Spread operator: It is a new operator added in ES6 version. It takes an iterable(array or string) and expands it into individual elements.
  • Rest parameter: It allows a function to accept indefinite number of arguments as an array.

Rest parameter

It allows a function to accept indefinite number of arguments as an array.

1
2
3
4
5
function solve(...foo) {
  console.log(foo); // [1, 2, 3, 4]
}

solve(1,2,3,4);
  • It is denoted by ...<variable_name>
  • There can be only one rest parameter passed as a function argument.

Before ES6 we had an arguments object, which is an array-like object. But is not a real array, which means array methods like map, filter, sort, forEach etc. can not be applied. Only length property works on arguments.

Whereas rest parameter is a real array and all the array methods can be applied to it directly.

arguments object

In this example, I have created a function solve and inside that, I am printing the arguments object, then its length and after that, I am trying to access a method that belongs to an array.

1
2
3
4
5
6
7
function solve() {
  console.log(arguments);
  console.log(arguments.length);  // 4
  console.log(arguments.pop());
}

solve(10, 20, 30, 40);

And now If you run this, you will see that, In the first console, it prints a structure that looks like an object.

1
2
3
4
5
6
{
  0: 10,
  1: 20,
  2: 30,
  3: 40
}

and we can access properties using square brackets, like this:

1
arguments[0]; // 10

and, there is an error on the third console statement because the arguments object is not a real array and only length property is accessible.

TypeError: arguments.pop is not a function

The typeof operator also returns object when used with arguments —

1
console.log(typeof arguments); // 'object' 

However, it can be converted to an array by writing —

1
var foo = Array.prototype.slice.call(arguments);

And now, If you try to access any array method on foo, you can do that.

1
2
3
4
5
6
7
8
9
10
function solve() {

  var foo = Array.prototype.slice.call(arguments);

  console.log(foo);
  console.log(foo.length);
  console.log(foo.pop());
}

solve(1,2,3,4);

rest parameter

Update the solve function, by adding a parameter with triple dot(...) as a prefix and name it whatever you want. I will name it as foo.

1
2
3
4
5
6
7
8
// foo acts as a rest parameter here
function solve(...foo) {
  console.log(foo);   // [10, 20, 30, 40]
  console.log(foo.length);    // 4
  console.log(foo.pop());   // 40 (removed and returned the last element from an array)
}

solve(10, 20, 30, 40);

And now If you run this, you can see that the output looks exactly like an array and all methods of the array are available to us.

Should be the last parameter

You can also pass more parameters to this function and you need to keep in mind that rest parameter should be the last parameter, it can not be placed at the front or in between.

1
2
3
4
5
6
7
function solve(a, ...foo, b) {
	console.log('a : ', a);
	console.log('foo : ', foo);
	console.log('b : ', b);
}

solve(1, 2);   // SyntaxError: Rest parameter must be last formal parameter

This code will throw an error.

SyntaxError: Rest parameter must be last formal parameter

Now, let’s fix this code by placing the rest parameter at the end of the parameter list.So, now add two more parameters called a and b, and if the argument length is more than two then only foo will have values, otherwise, it will be printed as an empty array.

1
2
3
4
5
6
7
function solve(a, b, ...foo) {
	console.log('a : ', a);   // 1
	console.log('foo : ', foo);   // []
	console.log('b : ', b);   // 2
}

solve(1, 2);

Now, if you add more arguments —

1
2
3
4
5
6
7
function solve(a, b, ...foo) {
	console.log('a : ', a);   // 1
	console.log('foo : ', foo);   // [3, 4]
	console.log('b : ', b);   // 2
}

solve(1, 2, 3, 4);

The remaining elements will become part of the rest parameter array.

Spread operator

It takes an iterable (array, object or string) and expands it into individual elements.

Some simple examples -

1
2
const shapes = ['square', 'circle', 'rectangle'];
console.log(...shapes);	// "square", "circle", "rectangle"

As you can see in this example - the array of strings have been expanded into their individual elements (strings).

Now, let us try the same for an array of numbers:

1
2
const digits = [1,9,8,2];
console.log(...digits);

In this example - an array of integers have been expanded into their individual elements.

Uses

Now, Let’s see some practical use cases of spread operator.

Copying array:

Spread operator can be used to create deep copies of an array or object, provided they are not nested.

Example —

Create an array shapes with 3 items in it and then copy this into another array called items by using the spread operator.

Now, if you modify any element of items, it won’t change the shapes array and this shows that the spread operator created a deep copy.

1
2
3
4
5
6
7
8
// Deep copy
let shapes = [1, 2, 3];
let items = [...shapes];

items[2] = 4;

console.log(shapes);    // [1, 2, 3]
console.log(items);    // [1, 2, 4]

Now, modify this example by adding one nested property in the shapes array and then follow the same process to copy shapes to the items array.

Now if you try to modify the nested property of items, it will change the shapes array and this shows that the spread operator creates a shallow copy in case of nesting. This holds same for objects as well.

1
2
3
4
5
6
7
8
9
// Shallow copy
let shapes = [1, 2, [3]];
let items = [...shapes];

items[2][0] = 4;

console.log(shapes);    // [1, 2, [4]]
console.log(items);    // [1, 2, [4]]
console.log(shapes === items);     // false

Cloning an object:

Just like copying arrays, it can also be used to clone objects. But it creates a deep copy only if the object is one level deep, otherwise a shallow copy would be made.

Array concatenation:

It can also be used to combine multiple arrays or arrays with other elements.

1
2
3
4
5
6
7
8
9
// combinining multiple arrays
let arr = [1, 2, 3];
let arr2 = [4, 5, 6];

// using concat method
console.log(arr.concat(arr2));    // [1, 2, 3, 4, 5, 6]

// using spread syntax
console.log([...arr, ...arr2]);    // [1, 2, 3, 4, 5, 6]

Extending an object/array:

New properties to an existing array or an object can be added by using the spread operator. It will also create a deep copy only if the array or object is one level deep.

1
2
3
4
5
6
// combinining object with other properties
let obj1 = { foo: 'bar', x: 42 };

let clonedObj = { ...obj1, z: 12 };

console.log(clonedObj);    // {foo: "bar", x: 42, z: 12}
1
2
3
4
5
// combinining arrays with other elements
const shapes = [1,2,3]
const items = [...shapes, 9, 10];

console.log(items);   // [1, 2, 3, 9, 10]

Finding min or max from an array:

The max or min method takes comma-separated integers instead of an array and find out the min or max. So if you are given an array then you can simply use spread operator.

1
2
3
4
let arr = [4, 7, 1, 2, 80, 0, 10, 9];

console.log(Math.max(...arr));    // 80
console.log(Math.min(...arr));    // 0

Summary

Rest parameter Spread operator
It allows a function to collect the number of arguments as an array It provides the ability to expand iterables
It can used in place of arguments object It can be used in place of concat(), Object.assign() method
Used an a function parameter It is passed as a function argument

Interview questions

Question 1: What are some of the new features introduced in ES6?

Along with let, const, arrow functions etc. a triple dot(...) operator was also introduced in ES6. It can be used in two different ways in JavaScript -- Rest parameter and Spread operator

Question 2: Differentiate between rest and spread operator.

Answer here

Question 3: Rest parameter vs arguments object.

Answer here

Question 4: Can we use spread operator to create a deep copy of an object/array?

Yes, it can be used to create a deep copy, provided there is no nesting in array or object.

-->