jsguides

Object.assign()

Object.assign(target, ...sources)

Object.assign() copies all enumerable own properties from one or more source objects into a target object and returns the modified target. It performs a shallow copy: primitive values are copied by value, but objects and arrays are copied by reference, so nested structures are shared between the original and the copy.

The operation is also used as the classic “merge” pattern: spreading multiple objects into a new empty object produces a merged result without mutating any of the inputs. This was the standard approach before the object spread syntax ({ ...a, ...b }) was introduced in ES2018.

Syntax

Object.assign(target, ...sources)

Parameters

ParameterTypeDescription
targetobjectThe target object that receives the copied properties
sourcesobjectOne or more source objects to copy from

Return value

The modified target object. Because target is mutated and returned, the return value and the original target are the same reference.

Examples

Basic object merging

const target = { a: 1 };
const source = { b: 2 };

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

console.log(target);
// { a: 1, b: 2 } — target is mutated!

The basic pattern above mutates the target directly, which is fine when you own the object. For cases where you need a fresh combined object without touching the originals, pass an empty object as the first argument and list your sources after it.

Merging multiple sources

const merged = Object.assign(
  { name: "JavaScript" },
  { version: "ES2024" },
  { feature: "top-level await" }
);

console.log(merged);
// { name: "JavaScript", version: "ES2024", feature: "top-level await" }

When multiple sources have the same key, later sources win. Properties are applied left to right, so the last source with a given key determines the final value. This ordering guarantee means you can layer overrides from most generic to most specific.

Shallow cloning an object

const original = { a: 1, b: 2 };
const clone = Object.assign({}, original);

console.log(clone);
// { a: 1, b: 2 }
console.log(clone === original);
// false

Passing an empty object {} as the target is the shallow-clone pattern. The clone is a new object with its own top-level properties, but any nested objects inside it still point to the same references as the original. For deeply nested structures, use structuredClone() instead.

Setting default options

const defaults = { theme: "light", lang: "en", fontSize: 14 };
const userOptions = { theme: "dark" };

const options = Object.assign({}, defaults, userOptions);
console.log(options);
// { theme: "dark", lang: "en", fontSize: 14 }

User options override defaults because userOptions comes after defaults. Neither input object is mutated because the target is a fresh {}. This pattern is widely used for configuration assembly where you need sensible defaults but must respect explicit user choices.

Common patterns

Avoiding mutation — pass {} as the first argument if you don’t want to change the inputs:

const obj = { x: 1 };
// This mutates obj:
Object.assign(obj, { y: 2 });

// This creates a new object and leaves obj alone:
const safe = Object.assign({}, obj, { y: 2 });

The safe pattern creates a new object each time, which is ideal for functional-style code where you want to avoid side effects. When you do want to mutate an existing object — for example, to set up instance properties in a constructor — pass this as the target.

Adding properties to an existing object:

class Config {
  constructor(opts) {
    Object.assign(this, opts);
  }
}

const cfg = new Config({ debug: true, port: 3000 });
console.log(cfg.port); // 3000

Because Object.assign(this, opts) directly sets properties on the instance, it skips setter traps and only copies enumerable own properties from the source. For plain data objects, the non-mutating form Object.assign({}, obj, { y: 2 }) keeps the original intact.

Object.assign() vs spread syntax

Both achieve the same shallow merge, but spread syntax is more concise for most cases:

// Object.assign
const merged = Object.assign({}, a, b);

// Spread syntax (ES2018+)
const merged = { ...a, ...b };

Use Object.assign() when the target must be an existing object (not a new one), or when you need dynamic source lists.

Why Object.assign() still matters

Object.assign() is still useful when you want to mutate a known target object in place or when the list of source objects is built dynamically at runtime. That makes it a good fit for configuration assembly, class constructors, and low-level merge helpers. It is also explicit about its mutation, which can be helpful when the caller should understand that the original target object is being updated.

Shallow copy caveat

The shallow behavior is important to remember: nested arrays and objects are copied by reference, not cloned. That means Object.assign() is safe for top-level property merges, but it is not a deep-clone tool. If the nested structure needs to be independent, use structuredClone() or another deep-copy strategy instead.

See Also