Back to JavaScript
JavaScript
easy
mid

How would you implement a polyfill for Object.assign?

Object.assign(target, ...sources) copies enumerable own properties from sources onto target, left to right, and returns target. Polyfill: validate target isn't null/undefined, Object() it, loop sources, copy own enumerable keys (use hasOwnProperty), return target. Note it's a shallow copy.

4 min read·~12 min to think through

Object.assign(target, ...sources) copies enumerable own properties from each source onto the target (left → right, later sources win) and returns the target. The polyfill tests whether you know those exact semantics.

The polyfill

js
function objectAssign(target, ...sources) {
  // 1. target can't be null/undefined
  if (target === null || target === undefined) {
    throw new TypeError("Cannot convert undefined or null to object");
  }

  // 2. coerce target to an object (primitives get boxed)
  const to = Object(target);

  for (const source of sources) {
    // 3. skip null/undefined sources (allowed, just ignored)
    if (source === null || source === undefined) continue;

    const from = Object(source);

    // 4. copy OWN ENUMERABLE properties only
    for (const key of Object.keys(from)) {
      to[key] = from[key];
    }
    // 4b. also copy own enumerable Symbol keys (full spec correctness)
    for (const sym of Object.getOwnPropertySymbols(from)) {
      if (Object.prototype.propertyIsEnumerable.call(from, sym)) {
        to[sym] = from[sym];
      }
    }
  }

  return to; // 5. return the (mutated) target
}

The semantics you must get right

  1. Throws on null/undefined targetObject.assign(null, ...) is a TypeError.
  2. **null/undefined sources are skipped** silently — not an error.
  3. Only own enumerable properties — inherited (prototype) properties and non-enumerable ones are not copied. Object.keys already filters to own + enumerable, which is why it's the right loop.
  4. Later sources overwrite earlier ones — left-to-right.
  5. Returns the target — and it mutates the target in place (that's why Object.assign({}, a, b) is the copy idiom — fresh {} as target).
  6. Symbol keys — full correctness copies own enumerable Symbol-keyed properties too.

The caveat: shallow copy

Object.assign copies values. For nested objects it copies the reference, not a deep clone — Object.assign({}, original).nested === original.nested. For deep copies, use structuredClone.

The framing

"Object.assign copies enumerable own properties from sources onto a target, left to right, and returns the mutated target. The polyfill: throw if the target is null/undefined, Object()-coerce it, loop the sources skipping null/undefined ones, and copy own enumerable keys — Object.keys conveniently gives exactly own+enumerable, and for full spec correctness also copy own enumerable symbols. The semantics that matter are: throws on a null target but skips null sources, own-enumerable-only, last write wins, returns target — and it's a shallow copy, so nested objects are shared by reference."

Follow-up questions

  • Why use Object.keys rather than a for...in loop?
  • What's the difference between a null target and a null source?
  • Why is Object.assign({}, a, b) the copy idiom?
  • Is Object.assign a deep or shallow copy?

Common mistakes

  • Using for...in, which also copies inherited enumerable properties.
  • Not throwing on a null/undefined target.
  • Throwing (instead of skipping) on null/undefined sources.
  • Forgetting to return the target.
  • Claiming it's a deep copy.

Performance considerations

  • O(total keys across sources). It's a shallow copy, so it's cheap relative to a deep clone — but mutating a large target repeatedly in a loop is O(n·m); prefer building one assign call.

Edge cases

  • Primitive target (gets boxed via Object()).
  • Symbol-keyed properties.
  • Getters on sources — they're invoked, the returned value is copied.
  • Non-enumerable properties — not copied.
  • Overlapping keys across sources — last wins.

Real-world examples

  • Merging default options with user-provided options.
  • Shallow-cloning objects for immutable updates (before object spread was common).

Senior engineer discussion

Seniors nail every semantic — null target throws, null sources skip, own-enumerable-only via Object.keys, returns the mutated target, symbol keys for full correctness — and proactively flag the shallow-copy behavior.

Related questions