Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

Spread and Rest Operators

Recommended resources

Hello! Welcome to the fourth lesson in the JavaScript Core Concepts module.

In our last lesson, we explored how destructuring can make your code cleaner and more readable by unpacking values from arrays and objects. You even got a sneak peek at the ... syntax when we used it to collect the "rest" of the elements in an array.

Today, we'll fully unpack the power of that three-dot syntax. This lesson is dedicated to mastering the spread and rest operators, fulfilling the learning outcome: Use the spread and rest operators to manipulate arrays and function arguments. Although they look identical, they perform opposite operations depending on where you use them. Mastering this duality is essential for writing modern, efficient JavaScript, especially when handling data in frameworks like React.

1. The Core Distinction: Collect vs. Expand

At first glance, seeing ... used for two different things can be confusing. The key is context. A simple rule of thumb is:

  • Rest Operator (...): Collects multiple elements and condenses them into a single element (an array). You'll find it in function parameter lists and on the left side of a destructuring assignment.
  • Spread Operator (...): Expands an iterable (like an array or string) into its individual elements. You'll find it in function calls, array literals, and object literals.

This image provides a great visual summary of this "opposite" relationship.

Spread vs. Rest Operators in JavaScript
This diagram shows the core difference: the spread operator (left) expands an iterable into individual arguments for a function, while the rest operator (right) collects individual arguments into a single array inside the function.

Let's explore each one in detail.


2. The Rest Operator: Gathering Remaining Values

You've already seen the rest operator in our last lesson on destructuring. It gathers all remaining elements of an array into a new array.

const scores = [95, 88, 76, 65, 54];

// The rest operator gathers the remaining scores into the 'restOfScores' array
const [topScore, secondScore, ...restOfScores] = scores;

console.log(topScore);      // 95
console.log(secondScore);   // 88
console.log(restOfScores);  // [76, 65, 54]

The most powerful use of the rest operator, however, is in function parameters. It allows a function to accept an indefinite number of arguments as an array. This is a modern replacement for the older arguments object and works seamlessly with arrow functions.

The following video explains this concept and its important rules very clearly.

ES6 Rest & Spread Explained Simply

The video 'ES6 Rest & Spread Explained Simply' from the DevSage channel provides a great explanation of the rest parameter. It clearly explains the syntax and the rules you must follow when using it.

Watch from the beginning to 05:57. Pay close attention to how the rest operator collects arguments into an array and the two critical rules: there can only be one rest parameter, and it must be the last parameter in a function's signature.

As the video highlights, the two rules are:

  1. A function can only have one rest parameter.
  2. The rest parameter must be the last parameter in the function definition.

Correct Usage:

// The 'tags' parameter will be an array containing all arguments after 'author'
function createPost(author, ...tags) {
  console.log(`Post by ${author}`);
  console.log('Tags:', tags); // 'tags' is a real array, so we can use methods like .join()
}

createPost('Alex', 'javascript', 'webdev', 'es6');
// Output:
// Post by Alex
// Tags: [ 'javascript', 'webdev', 'es6' ]

Incorrect Usage:

// SyntaxError: Rest parameter must be last formal parameter
function incorrectFunction(...tags, author) {
  // ...
}
Test your understanding!

Write a function calculateAverage that takes a subject name (string) as its first argument, followed by any number of scores. The function should return a string like "The average for Math is 85.". If no scores are provided, it should return "No scores provided for [subject].".

Hint: You'll need to use the rest parameter to collect the scores into an array and then check if that array is empty.

Show answer
function calculateAverage(subject, ...scores) {
  if (scores.length === 0) {
    return `No scores provided for ${subject}.`;
  }

  // Since 'scores' is an array, we can use array methods like reduce!
  const total = scores.reduce((sum, current) => sum + current, 0);
  const average = total / scores.length;

  return `The average for ${subject} is ${average}.`;
}

console.log(calculateAverage('Math', 90, 80, 85)); // "The average for Math is 85."
console.log(calculateAverage('History')); // "No scores provided for History."

3. The Spread Operator: Expanding Iterables

The spread operator does the opposite of rest: it takes an iterable (like an array) and "spreads" its elements out. It is incredibly useful for creating new arrays and objects immutably, a core principle in modern web development.

This short video gives a quick tour of its most common use cases.

...spread operator and rest operator - Beau teaches JavaScript

The video '...spread operator and rest operator' by Beau Carnes from freeCodeCamp.org quickly demonstrates the primary uses of the spread operator with arrays.

Watch from 00:25 to 04:26. Focus on how spread is used for: adding elements to a new array, passing elements as function arguments, copying an array, and concatenating arrays.

Let's review those key applications.

Combining and Copying Arrays

The spread operator provides a highly readable way to create new arrays by combining or copying existing ones.

const frontend = ['react', 'vue', 'angular'];
const backend = ['node', 'express', 'postgres'];

// Combining arrays
const fullstack = [...frontend, ...backend];
console.log(fullstack); // ['react', 'vue', 'angular', 'node', 'express', 'postgres']

// Creating a shallow copy
const frontendCopy = [...frontend];
console.log(frontendCopy); // ['react', 'vue', 'angular']

// Adding elements immutably
const updatedFrontend = ['svelte', ...frontend];
console.log(updatedFrontend); // ['svelte', 'react', 'vue', 'angular']

Working with Objects

The spread syntax is also invaluable for working with objects. You can use it to clone objects or merge them. This is the go-to pattern for updating state in React. When merging, properties from later objects in the sequence will overwrite properties from earlier ones.

const userDefaults = {
  theme: 'light',
  notifications: true,
  isAdmin: false
};

const userOverrides = {
  theme: 'dark',
  username: 'js_dev'
};

// Merge objects. Properties from userOverrides will win.
const finalSettings = { ...userDefaults, ...userOverrides };

console.log(finalSettings);
// { theme: 'dark', notifications: true, isAdmin: false, username: 'js_dev' }

A Critical Note: Shallow vs. Deep Copy

When you use the spread operator to copy an array or object, it creates a shallow copy. This is a crucial concept. It means that while the top-level structure is new, any nested arrays or objects are simply referenced, not copied.

For a deeper dive into all the uses of spread and rest, including this important detail about shallow copies, the following article is an excellent resource.

JavaScript Rest vs Spread Operator – What's the Difference?

The article 'JavaScript Rest vs Spread Operator' provides a comprehensive text-based explanation of both operators. Pay special attention to the section on how spread works with non-primitive values, as this is a common source of bugs for developers.

Read the section titled 'What Is the Spread Operator and How Does spread work in JavaScript?'. Focus on the examples and the three key pieces of info under 'What to Know About the Spread Operator', especially 'Info 3: Beware of how spread works when used on objects containing non-primitives!'.

As the article explains, if you modify a nested object in the copied structure, the change will also appear in the original!

const original = { name: 'Admin', permissions: { canEdit: true } };
const copy = { ...original };

// Modify the nested object in the copy
copy.permissions.canEdit = false;

// The original is also changed!
console.log(original.permissions.canEdit); // false

This happens because both original.permissions and copy.permissions point to the exact same object in memory.


Conclusion

You've now seen how the ... syntax pulls double duty as two of the most useful operators in modern JavaScript. While they look the same, their behavior is opposite and determined entirely by context.

Key Takeaways:

  • Rest Operator (...): Collects elements into an array. Use it in function parameters to create functions that accept a variable number of arguments, or in destructuring to gather remaining items.
  • Spread Operator (...): Expands iterables (like arrays, strings, and objects) into their individual elements. Use it to cleanly combine arrays, create shallow copies of arrays/objects, and pass array elements as individual arguments to a function.
  • Shallow Copy Warning: Remember that spread creates a shallow copy. Nested objects and arrays are referenced, not duplicated. Modifying a nested object in a copy will affect the original.

In our next lesson, we will continue building your data manipulation skills by exploring the core higher-order array methods: map(), filter(), and reduce(). You'll find that the spread and rest operators are perfect companions to these methods for creating powerful and readable data transformations.