Knowledge Base/concepts/JavaScript Spread Operator

JavaScript Spread Operator

The JavaScript spread operator, written as ..., expands values into a place that expects individual items or properties. It is wonderfully convenient and slightly dangerous. Spread can make copying and merging look effortless, but it only creates shallow copies. If nested objects are involved, the top level is new and the deeper references are still shared.

That is the difference between "this copied" and "this copied enough for the bug to wait until Friday."

Spread in arrays

Array spread expands an iterable inside an array literal:

const start = [1, 2];
const end = [3, 4];

const combined = [...start, ...end];

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

You can mix spread values with individual values:

const numbers = [2, 3];
const result = [1, ...numbers, 4];

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

The order is exactly the order written.

Copying arrays

Spreading an array into a new array literal gives you a new top-level array:

const original = ["red", "green"];
const copy = [...original];

copy.push("blue");

console.log(original); // ["red", "green"]
console.log(copy); // ["red", "green", "blue"]

That is useful when you want to avoid mutating the original array.

It is not a deep copy:

const users = [{ name: "Ada" }, { name: "Grace" }];
const copy = [...users];

copy[0].name = "Changed";

console.log(users[0].name); // "Changed"

The array is new. The objects inside it are the same objects.

Merging arrays

Spread replaces many old concat calls:

const published = ["closures", "arrays"];
const drafts = ["fetch", "modules"];

const allPosts = [...published, ...drafts];

It does not remove duplicates:

const a = [1, 2, 3];
const b = [2, 3, 4];

const merged = [...a, ...b];

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

Use Set when uniqueness is the goal:

const unique = [...new Set([...a, ...b])];

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

Spread in function calls

Function-call spread expands an iterable into positional arguments:

const scores = [88, 94, 72];

console.log(Math.max(...scores)); // 94

Before spread, you often saw apply for this:

Math.max.apply(null, scores);

Spread makes the call site clearer. The array is unpacked into normal arguments.

Spread in objects

Object spread copies own enumerable properties into a new object:

const user = {
  id: 1,
  name: "Ada",
};

const copy = { ...user };

You can add or override properties:

const updated = {
  ...user,
  name: "Ada Lovelace",
  active: true,
};

console.log(updated);
// { id: 1, name: "Ada Lovelace", active: true }

The last property wins. That makes order important:

const defaults = { theme: "light", pageSize: 20 };
const userPrefs = { pageSize: 50 };

const settings = { ...defaults, ...userPrefs };

console.log(settings);
// { theme: "light", pageSize: 50 }

Spread defaults first, then overrides. Reverse the order and your defaults will overwrite the user, which is a bold design choice and usually a bad one.

Shallow copy is the main caveat

Object spread is shallow too:

const original = {
  name: "Ada",
  profile: {
    city: "London",
  },
};

const copy = { ...original };

copy.profile.city = "Paris";

console.log(original.profile.city); // "Paris"

If you update nested data, copy each level you change:

const updated = {
  ...original,
  profile: {
    ...original.profile,
    city: "Paris",
  },
};

If you need a full deep copy of supported data types, structuredClone() may be the right tool:

const deepCopy = structuredClone(original);

Do not use JSON stringify/parse as a casual deep-copy tool unless you are happy to lose functions, undefined, dates, maps, sets, and several other perfectly real values.

Object spread is not iterable spread

These two forms look similar but work differently:

const arr = [..."abc"];
const obj = { ...{ a: 1, b: 2 } };

Array and function-call spread require iterables:

const chars = [..."abc"];
const fromSet = [...new Set([1, 2, 3])];

Plain objects are not iterable:

const config = { host: "localhost" };

[...config];
// TypeError: config is not iterable

Object spread copies properties inside an object literal:

const configCopy = { ...config };

Same token, different context.

Object.assign versus spread

Object.assign is still common in older code:

const merged = Object.assign({}, defaults, userPrefs);

The spread version is usually easier to read:

const merged = { ...defaults, ...userPrefs };

The easy mistake with Object.assign is forgetting the empty target:

Object.assign(defaults, userPrefs);

That mutates defaults. Object spread always creates a new object literal, so there is no target object to accidentally mutate.

Key Takeaways

  • Spread expands array items, iterable values, function-call arguments, or object properties depending on context.
  • Array and object spread create new top-level containers.
  • Spread copies are shallow; nested objects remain shared unless you copy those levels too.
  • Object merge order matters because the last property wins.
  • Use spread for readable copying and merging, but use structuredClone() or explicit nested copies when deep independence matters.
Share this article:
javascriptfundamentalsarraysobjects