How to Use Object Assign in JavaScript Properly

Learn how Object.assign() copies properties between objects. Use it for merging configuration, cloning objects, and setting defaults, while avoiding the shallow copy trap.

6 min read

Object.assign() in JavaScript copies properties from one or more source objects into a target object. It is the built-in way to merge objects, apply default values, or create a shallow clone.

The method mutates the target object and returns it. This is different from the spread operator, which creates a new object. Understanding this distinction helps you choose the right tool for each situation.

Basic Syntax and Return Value

The method takes a target object first, followed by any number of source objects. Every property from each source gets copied onto the target, in the order the sources are listed:

javascriptjavascript
Object.assign(target, ...sources)
ParameterDescription
targetThe object to copy properties into. This object is mutated.
sourcesOne or more objects whose properties are copied into target.
ReturnsThe target object after all sources have been merged in.

Here is the simplest case:

javascriptjavascript
const target = { a: 1 };
const source = { b: 2 };
 
const result = Object.assign(target, source);
 
console.log(target); // { a: 1, b: 2 } -- mutated
console.log(result); // { a: 1, b: 2 } -- same reference as target
console.log(result === target); // true

The return value is the same object as target. This is useful for chaining or passing the merged result directly to another function.

Merging Multiple Objects

You can pass more than one source object. Later sources overwrite properties from earlier sources when keys collide:

javascriptjavascript
const defaults = { theme: "light", fontSize: 14, showSidebar: true };
const userPrefs = { theme: "dark", fontSize: 16 };
const sessionPrefs = { fontSize: 18 };
 
const finalConfig = Object.assign(defaults, userPrefs, sessionPrefs);
 
console.log(finalConfig);
// { theme: "dark", fontSize: 18, showSidebar: true }

The order matters. Each later source overwrites matching keys from the sources before it, so sessionPrefs has the final say and defaults is overwritten first. A property that only exists in one object, like showSidebar, is kept untouched.

This pattern is common for merging configuration or applying cascading user preferences.

Cloning a Flat Object

Object.assign() can create a shallow copy by passing an empty object as the target:

javascriptjavascript
const original = { x: 10, y: 20, label: "point" };
const clone = Object.assign({}, original);
 
console.log(clone);          // { x: 10, y: 20, label: "point" }
console.log(clone === original); // false (different references)
 
// Changing the clone does not affect the original for top-level properties
clone.x = 99;
console.log(original.x); // 10 (unchanged)

This works well for flat objects where every value is a primitive (string, number, boolean, null, undefined). But be careful with nested objects.

The Shallow Copy Trap

Object.assign() copies property values as-is. When a property value is an object or array, only the reference is copied:

javascriptjavascript
const original = {
  name: "Project Alpha",
  metadata: { created: "2026-01-10", version: 2 }
};
 
const clone = Object.assign({}, original);
 
clone.name = "Project Beta";
console.log(original.name); // "Project Alpha" (unchanged)

Changing a top-level primitive on the clone leaves the original untouched, because primitives are copied by value. Nested objects behave differently, since only the reference to them gets copied:

javascriptjavascript
clone.metadata.version = 3;
console.log(original.metadata.version); // 3 (changed in both!)

Both original and clone now point to the same metadata object. Changing it through one reference affects the other.

For deep cloning, where nested objects are recursively copied, use structuredClone() (available in modern browsers and Node.js 17+):

javascriptjavascript
const deepClone = structuredClone(original);
deepClone.metadata.version = 99;
 
console.log(original.metadata.version); // 2 (safe)
console.log(deepClone.metadata.version);  // 99

For more cloning techniques, see the article on /javascript/how-to-clone-a-javascript-object-without-errors.

Applying Defaults Without Overwriting Existing Values

Sometimes you want to fill in missing properties without changing the ones that already exist. You can do this by putting the defaults first and the existing values last:

javascriptjavascript
const userProvided = { name: "Sam", email: "sam@example.com" };
const defaults = { name: "Guest", email: "", role: "user" };
 
const merged = Object.assign({}, defaults, userProvided);
 
console.log(merged);
// { name: "Sam", email: "sam@example.com", role: "user" }

The name and email fields come from userProvided, since it is listed last and its values overwrite the defaults. The role field comes from defaults because the user did not provide one.

If you reverse the order, the defaults would overwrite the user's values, which is not what you want.

Object.assign() vs the Spread Operator

The spread operator ... is a newer, often cleaner alternative to Object.assign(). Both produce the same merged result for a fresh object:

Object.assign()Spread ({ ...a, ...b })
Creates a new objectOnly if target is an empty objectAlways
Can mutate an existing objectYesNo
Typical useMerging into an existing targetBuilding a brand new merged object
javascriptjavascript
const a = { x: 1, y: 2 };
const b = { z: 3 };
 
console.log(Object.assign({}, a, b)); // { x: 1, y: 2, z: 3 }
console.log({ ...a, ...b });          // { x: 1, y: 2, z: 3 }

The spread operator always creates a new object. Object.assign() can merge into an existing object. Use spread when you want a fresh object, and Object.assign() when you need to mutate an existing one:

javascriptjavascript
// Mutate an existing object
Object.assign(existingConfig, newSettings);
 
// Create a new object (spread is cleaner)
const newConfig = { ...existingConfig, ...newSettings };

For most everyday merges, the spread operator is the more readable choice. For more on spread syntax, see the article on /javascript/js-spread-operator-for-arrays-complete-tutorial.

Common Mistakes

Mutating the target by accident. If you pass an object you did not mean to change as the first argument, it gets modified:

javascriptjavascript
const defaults = { timeout: 3000 };
const options = { timeout: 5000 };
 
Object.assign(defaults, options);
console.log(defaults.timeout); // 5000 -- defaults was mutated

To avoid this, pass an empty object first: Object.assign({}, defaults, options).

Expecting a deep copy. Object.assign() only does a shallow copy. Nested objects and arrays are shared between the original and the result. Use structuredClone() for deep copies when you need isolation.

Forgetting that null and undefined sources are skipped. You can safely pass them without errors:

javascriptjavascript
const result = Object.assign({}, { a: 1 }, null, undefined, { b: 2 });
console.log(result); // { a: 1, b: 2 }

Overwriting with the wrong property order. If you put defaults last, they overwrite the values you actually wanted to keep, which quietly discards whatever the user or caller provided:

javascriptjavascript
// Wrong: defaults overwrite the user's values
Object.assign({}, userPrefs, defaults);
 
// Correct: user values overwrite defaults
Object.assign({}, defaults, userPrefs);
Rune AI

Rune AI

Key Insights

  • Object.assign(target, ...sources) copies own enumerable properties from sources into target.
  • It mutates the target object and returns it.
  • Later sources overwrite properties from earlier sources with the same key.
  • It performs a shallow copy: nested objects are shared, not cloned.
  • For immutable merges that create a new object, use the spread operator instead: { ...a, ...b }.
RunePowered by Rune AI

Frequently Asked Questions

Does Object.assign() create a deep copy?

No. Object.assign() creates a shallow copy. Top-level properties are copied, but nested objects and arrays are shared by reference. Modifying a nested object in the copy also modifies the original.

Should I use Object.assign() or the spread operator?

For creating a new merged object, prefer the spread operator: { ...target, ...source }. It is more readable. Use Object.assign() when you need to mutate an existing target object in place.

What happens if two source objects have the same property?

Later sources overwrite earlier ones. The rightmost object's value for a given key wins.

Conclusion

Object.assign() is a workhorse for copying properties from one or more source objects into a target object. Use it when you need to merge configurations, apply defaults, or clone flat objects. Remember that it only does a shallow copy, so nested objects and arrays are shared by reference. For most one-shot merges where you create a new object, the spread operator is the cleaner choice.