Skip to content

Instantly share code, notes, and snippets.

@peplocanto
Created May 17, 2023 09:14
Show Gist options
  • Save peplocanto/ec05b251d9f737c246490f7738699a8f to your computer and use it in GitHub Desktop.
Save peplocanto/ec05b251d9f737c246490f7738699a8f to your computer and use it in GitHub Desktop.

Attack of the Clones: Deep Copy vs. Shallow Copy

In JavaScript (and TypeScript) understanding the concepts of deep copy and shallow copy is essential for maintaining data integrity.

Considering these models:

interface Jedi {
  name: string;
  species: Species;
  powers: Powers;
}

interface Powers {
  abilities: string[];
}

type Species = 'Human' | 'Alien';

The Essence of Shallow Copy

Shallow copy entails creating a new object and copying property values from the original object into the new one. However, it falls short when it comes to preserving the integrity of nested objects. Let's examine a scenario that highlights the limitations of shallow copy:

const anakin: Jedi = {
  name: "Anakin Skywalker",
  species: 'Human',
  powers: {
    abilities: ["Lightsaber combat", "Force push"],
  },
};

const darthVader: Jedi = {...anakin}; // Shallow copy

darthVader.name = "Darth Vader";
darthVader.powers.abilities.push("Force choke");

console.log(anakin.name); // Output: "Anakin Skywalker"
console.log(anakin.powers.abilities); // Output: ["Lightsaber combat", "Force push", "Force choke"]

As shown by outputs spread syntax effectively goes one level deep while copying an object. Therefore, it may be unsuitable for copying nested object. The same is true with Object.assign() and Object.create().

Unleashing the Power of Deep Copy

To achieve a true and independent copy of an object, including nested properties, we need to employ deep copy techniques. We can leverage serialization and deserialization to accomplish this. Let's examine an approach using JSON.stringify() and JSON.parse():

const luke: Jedi = {
  name: "Luke Skywalker",
  species: "Human",
  powers: {
    abilities: [],
  },
};

const trainedLuke: Jedi = JSON.parse(JSON.stringify(luke)); // Deep copy with JSON.stringify and JSON.parse

trainedLuke.powers.abilities.push("Lightsaber combat", "Force push");

console.log(luke.powers.abilities); // Output: []

As shown by output after the serialization/deserialization process a deep copy shares no references with its source object, any changes made to the deep copy do not affect the source object.

A new Ho...pi

Until now no native operation in JavaScript does a deep clone but the new web API method structuredClone() allows deep copying values of certain supported types. Let's examine an approach using this api:

const yoda: Jedi = {
  name: "Yoda",
  species: "Alien",
  powers: {
    abilities: ["Lightsaber combat", "Force push"],
  },
};

const trainedYoda: Jedi = structuredClone(yoda); // Deep copy with structuredClone

trainedYoda.powers.abilities.push("Speaking clearly");

console.log(yoda.powers.abilities); // Output: ["Lightsaber combat", "Force push"]

As developers, just as we grasp the distinction between pointers and values in programming, understanding the difference between deep copy and shallow copy becomes crucial when handling complex objects. Shallow copy mirrors the surface, while deep copy empowers us to create independent replicas, even for nested objects. Equipped with this knowledge, we ensure data consistency and uphold the integrity of our code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment