Have you ever sat down to write a bit of JavaScript thinking, “this is easy; shouldn’t take more than a few minutes,” and then six hours later there you are with a bottle of whisky in one hand and a sharpened stick in the other shouting curses in Latin at StackOverflow answers?
Obviously that’s a rhetorical question because all web developers have at some point learned the hard way how to copy objects in JavaScript.
Let’s talk about why it’s hard and weird and what we can do about it (that doesn’t involve invoking ancient curses (probably))

You don’t copy an object by reassigning variables
At some point in your JavaScript journey, you might be tempted to think that you can copy an object just by assigning it to a new variable, like so:
const frank = {
name: {
first: 'Frank',
last: 'Taylor'
},
handle: 'Paceaux'
};
const clone = frank;
Yeah that’s all well and good. And then you’ll modify the copy:
clone.name.first = "Bizarro Frank";
And everything is fine, so long as you never go back and look at the original variable:
It’s totally natural to think, “oh, I’ll just assign this variable again,” and then believe, “I’ve copied it.”
What’s really hard is that this is not what JavaScript thinks. JavaScript has different plans for you.
JavaScript passes variables by value
Let’s do some experiments, shall we?
Nothing weird about strings, right?
const thread = "some string"; let fabric = thread; console.log(fabric); // "some thread" fabric = "some yarn"; console.log(fabric, thread); // "some yarn"
- strings are immutable
- All we did was assign
fabricto a new value
Nothing weird about numbers, right?
const digit = 42; let integer = digit; console.log(integer); // 42 integer++; console.log(digit, integer); // 42, 43
- The variables each referred to a specific number
- When we incremented
integer, we made it reference a new number value
What about booleans?
const veracity = true; let maybe = veracity; console.log(maybe); // true maybe = false; console.log(veracity, maybe); // true, false
- The variables each referred to a specific boolean value
- When we reassigned
maybe, we made it reference a new boolean value
It’s about the thing you’re referring to
Let’s repeat our experiment with an array:
const nouns = ['person', 'place'];
let moreNouns = nouns;
console.log(nouns); // ['person', 'place'];
moreNouns.push('thing');
console.log(nouns, moreNouns); // ['person', 'place', 'thing'], ['person', 'place', 'thing'];
- the variables each referred to a specific value, which was an array
- So
moreNounsreferred not to nouns, but to['person', 'place'] - So when we updated
moreNouns, we updated the array to which it was referring
You don’t get a new object when you make a new variable because you don’t get a new house when your street name changes. They’re both just references to a location.
Koko, from her lesser-known fourth book, “Why I sharpened this stick”
But then how do you copy a friggin’ object in JavaScript?
Object.assign, obviously
const frank = {
name: {
first: 'Frank',
last: 'Taylor'
},
handle: 'Paceaux'
};
const clone = Object.assign({}, frank);
clone.handle = "xuaecap";
console.log(frank.handle, clone.handle); // 'Paceaux', 'xuaecap';
Problem solved, right? Just use Object.assign(), make the first argument an empty object, and the second object the original.
Case closed, problem solved, let’s go home.
Our problems have only begun
Object.assign has a teeny tiny lil’ pitfall (or two (or three)).

Object.assign doesn’t help with nested objects
Let’s repeat our little experiment, but this time on the name object.
const frank = {
name: {
first: 'Frank',
last: 'Taylor'
},
handle: 'Paceaux'
};
const clone = Object.assign({}, frank);
clone.name.first = "Knarf";
console.log(frank.name.first, clone.name.first); // 'Knarf', 'Knarf';
- We copied property values
- if a property value is an object, what we copy is a reference to the object
- So
frank.nameandclone.nameactually both have the same reference to the same object
Object.assign is an agent of chaos if you’re using getters and setters
const frank = {
name: {
first: 'Frank',
last: 'Taylor'
},
handle: 'Paceaux',
get fullname() {
return `${this.name.first} ${this.name.last}`;
}
};
const clone = Object.assign({}, frank);
clone.name.first = "Knarf";
// pay very close attention to what you're about to see next:
console.log(frank.fullname); // 'Knarf Taylor'
console.log(clone.fullname); // 'Frank Taylor'
Time to start swearing some more
- We copied property values
- We copied a reference to an object
Object.assigndoesn’t copy your fancy dynamic getters, it copies their values- so the clone got the computed value for
fullname, which was “Frank Taylor”
- so the clone got the computed value for
- Meanwhile the original object still had a dynamic its dynamic getter
- And both variables refer to the same
nameobject, which we updated - So the original object returns “Knarf Taylor” for
fullname
- And both variables refer to the same
Object.create can solve the problem with copying getters and setters
const frank = {
name: {
first: 'Frank',
last: 'Taylor'
},
handle: 'Paceaux',
get fullname() {
return `${this.name.first} ${this.name.last}`;
}
};
const clone = Object.create(frank);
clone.name.first = "Knarf";
console.log(frank.fullname, clone.fullname); // 'Knarf Taylor', 'Knarf Taylor';
- Still copied those property values
- Still copied a reference to an object
Object.createcopied the dynamic getter, though- So the clone didn’t get the computed value, but the dynamic getter
- Meanwhile, the original object is still the original
- Both variables refer to the same
nameobject, which we updated - So now the original and the clone both return “Knarf Taylor” for
fullname
- Both variables refer to the same
And then there’s all the other problems with Object.assign
- Anything that’s non-enumerable isn’t copied
- anything that’s on the prototype chain is also ignored
- Any properties set to
writable: falsewill throw an exception that interrupts the copy task
So Object.assign is only good for plain-jane ordinary ol’ objects that are one level deep.
So how are you supposed to copy an object with nested objects?
Yeah so there’s copying, and then there’s deep copying.
We’re talking about deep copying.
Handle it as JSON
const frank = {
name: {
first: 'Frank',
last: 'Taylor'
},
handle: 'Paceaux'
};
const clone = JSON.parse(JSON.stringify(frank));
clone.name.first = "Knarf";
console.log(frank.name.first, clone.name.first); // 'Frank', 'Knarf'
- We stringified the object
- We then converted that string back into an object (i.e. we created a new object)
- Which means that
frank.nameandclone.namereference totally different objects
- Which means that
The only problem here is the one you’re already thinking of…
How does the JSON stringify thing work if you have dynamic getters and setters?
const frank = {
name: {
first: 'Frank',
last: 'Taylor'
},
handle: 'Paceaux',
get fullname() {
return `${this.name.first} ${this.name.last}`;
}
};
const clone = JSON.parse(JSON.stringify(frank));
clone.name.first = "Knarf";
console.log(frank.fullname, clone.fullname); // 'Frank Taylor', 'Frank Taylor';
Now is when you need to get up from your computer and go hug a loved one.
- We stringified the object
- So stringification means we copied the computed value
- Which means we copied the result of
fullnameinto the clone
- Which means we copied the result of
Sooo… the JSON trick maybe isn’t great for dynamic getters and setters.
And this is why it’s so hard to copy an object in JavaScript
The StackOverflow answers on this aren’t exactly fun reads. You can practically hear the desperation in the answers and feel the very sharp sticks they’ve all crafted from chair legs. Every answer has gotchas that essentially amount to:
- JavaScript references by value in variable assignments and object assignments (i.e. objects nested on objects)
- See above
You see, getters and setters are this kind of internal function (a type of description) that lives on an object.
And functions are objects.
And as we’ve definitely figured out by now, JavaScript points to references of objects not their values.
But there is hope*
There now exists structuredClone, which is exactly what you think it is. It’s a deep cloning function.
The catch, though, is that it’s not part of the JavaScript specification. It’s part of the DOM. So if you want to use it in a non-browser environment, you’re going to need a polyfill.
Of course, you could also use a Lodash library or a good ol’ fashioned NPM package.
(Almost) No one will judge you.
Sources and whatnots
- Koko was a gorilla that learned sign language
- Koko had three books: Koko’s Kitten, Koko’s Story, The Education of Koko
- A group of gorillas is called a troop. So is a group of guerillas.

