Before we jump directly in Pass by Value and Pass by Reference, lets talk about two important data type which are :-
Primitive Values
Primitive Values includes Numbers, Booleans, null , undefined and strings (although C++ doesn't accounts strings as a primitive type),
So when you assign a primitive value to a variable the actual value is directly stored in the variable,
Whenever you put the variable with the value equal to another variable or pass the variable in a function as an argument then its value is directly sent to the next variable or function.
It creates a copy of the original value and whatever changes made in the new assigned variable will not affect the original variable.
Lets see an example of this
let x = 5
let y = x //assigned x's value to a new variable
y = y+6 // updated the value of y
console.log(y,x) // 11 5
In the above example we can see that we assigned x's value to a new variable and whatever changes we did to the new variable didn't affected our original variable x.
This concept is what we call Pass by Value.
Non-Primitive Values
Non-Primitive Values includes objects, arrays, functions,
When you assign a Non-Primitive Value or reference value to another variable, then it doesn't really work like our primitive values where the value is copied and directly sent to the other variable or the function,
Instead it sends a reference or an address of the variable where it was original stored. So whatever changes you make to the new variable, it will directly affect the original variable.
Lets see an example of this
let x = {name:'john', gender:'male'}
let y = x //assigned x's value to a new variable
y.name = 'bob' // updated the value of y
console.log('y:',y,'\n','x:',x)
// y: { name: 'bob', gender: 'male' }
// x: { name: 'bob', gender: 'male' }
In the above example we can see that we assigned x's value which is an object this time to a new variable and assigned a new value to one of its property a new value and the same change is applied to our original object.
This concept is what we call Pass by Reference.
So in case of function basically passing by reference means that the function receives a reference to the original value allowing modification made within the function to affect the original value.
If you have worked with React you must have seen this in props where if you pass a function as prop and then call that function in the child component then the function in the parent component is called, How does that work? It is also because of Pass by Reference because Functions also work as Pass by reference so the actual address of our function is still exists in the Parent component so that's why the function gets called in the Parent component.
Now one question that really bugged me while studying this topic was , why?
Why does primitive values are stored as pass by value and not by reference and vice versa for non primitive values.
After Searching the web and talking to some people I came to know that Primitive values such as numbers are stored directly in variables because they have a fixed size and are simple basic data types.
Since there size is known and fixed, they can be easily stored directly in memory location associated with the variable.
Now what do I mean by 'Fixed' ?
When we say fixed it means that the memory required to store that value is predetermined and consistent across instances of the primitive type.
For example :-
The int data type is a primitive data type, regardless of the specific value being stored an int value will always occupy 4 bytes of memory.
But in case of an object (a non primitive type) it can contain multiple properties related to that object. Like for example an object which is name 'student' can have multiple properties like name, gender, phone number and many more properties.
So the size of the object will depend on the number of properties.
So Where are these Non-Primitive values stored?
Heaps , these values are stored in heaps and there location/address is stored in the variable. And so when this variable's value is re assigned to another value then it simply sends the address which was assigned to the first variable and that's why the changes are affected in the original variable too.
Now that we know about Pass by Value and Pass by Reference and we know that in our non primitive values any change made to a re assigned variable will affect the original value, the next question that came in my mind, was
"What if I want to modify the object but don't want to make any changes in the original variable?'
To Solve this problem I get to know about Shallow Copy and Deep Copy
So first lets talk about :-
Shallow Copy
As the name suggests its a copying technique which is shallow or not too deep, so what does it mean in Programming terms?
Well first thing before shallow copying an object lets see how this copying is any different than our normal way where we just re assign the variable like this
let x = {name:'john'}
let y = x
Well when shallow copying or deep copying what we do different is we loop through our original object and put new values in our new object one by one.
When you use a loop to create a shallow copy of an object in JavaScript, you are essentially creating a new object with new references in memory for each property that you copy.
You can use different techniques to loop which varies from using 1. Rest operator in JavaScript
- Map(will need an array of objects for this) in JavaScript
- Simple for loop.
Let's see an example:-
let x = {name:'john', gender:'male'}
let y = {...x}
y.name = 'as'
console.log('y:',y,'\n','x:',x)
// y: { name: 'as', gender: 'male' }
// x: { name: 'john', gender: 'male' }
Here we can see that we used rest operator to transfer elements one by one and then made changes in that, so those changes didn't affected the original object.
Solved our problem right? Not really lets see an example
const originalObject = { a: 1, b: { c: 2 } };
const shallowCopyObject = {...originalObject}
shallowCopyObject.a = 10; //updating top level value
shallowCopyObject.b.c = 20; //updating nested value
console.log(originalObject); // Output: { a: 1, b: { c: 20 } }
console.log(shallowCopyObject); // Output: { a: 10, b: { c: 20 } }
Here we can see that the value of 'a' changed and didn't affected the original object but the same didn't applied to the nested one.
When you create a shallow copy of an object using the spread syntax, a new object is created with new references to the top-level properties of the original object. However, any nested objects or arrays within the original object are still referenced by both the original object and the shallow copy.
Why does this happens?
This happens because of how data is stored in heaps. JavaScript doesn't really creates a "Nest" in our heap here is an illustration of how it actually stores it
Object :-
const obj = {
prop1: "hello",
prop2: {
nestedProp1: "world"
}
}
Heap Structure :-
Memory location 0x0001:
{
prop1: "hello",
prop2: 0x0002
}
Memory location 0x0002:
{
nestedProp1: "world"
}
So to solve this problem what we use is
Deep copying
So to Deep Copy the basic approach that I found is to Recursively loop through the Object and copy each level instead of directly just copying the first level, this way every level gets a new reference , but this way can be very costly and Inefficient
Here is a code I got from Chat GPT of doing Deep copying manually
function deepCopy(obj) {
// If obj is not an object or is null, return obj
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// Create a new object with the same prototype as obj
const newObj = Object.create(Object.getPrototypeOf(obj));
// Iterate over each property of obj
for (const prop in obj) {
// Check if prop is an own property of obj
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// If prop is an object, recursively call deepCopy to create a new reference to the nested object's properties
newObj[prop] = deepCopy(obj[prop]);
} else {
// If prop is not an object, create a new reference to the property value
newObj[prop] = obj[prop];
}
}
return newObj;
}
This will return a deep copied Object and you can make any changes to that now.
In my recent project which is Notion Clone I have this deeply nested object which is like a tree structure which I needed to manipulate frequently, and if I use the above technique for CRUD operations in my tree then it would have been really inefficient.
So rather what I used was not exactly deep copying but a using shallow copy but only at the necessary level.
Here is an image of how my tree looked like
Now this is just one branch of my tree, and I had multiple branches , so you can imagine how big the tree was , and so the idea to deep copy it was kind of a problem.
So what I did was talked to some people on discord servers and they told me an approach which I updated a little to my needs to come up with a logic
You can check out the logic here for now , Github Link
This link will take you directly to the component where I implemented it (in React).
Here is the Replit Link if you want to try using the simplified version :- Replit Link
So basically what I did was recursively looped through the branch where my target is till I found the exact branch which is needed to be modified (for the case of updating and adding). Once that branch is found I make the change in that branch using the spread operator so it makes a shallow copy of that object only at that level and returns it back and so on till the function ends.
Now this is a very simple explanation of what I did because in my approach I found a way through which I knew in advance what's the root branch (not parent branch , root branch) of every nested children.
And using that only looped through the branch which actually contains the target , So it was a blend of BFS (Breadth First Search) and DFS (Depth First Search)
After that I send that branch onto the next function which recursively loops through the branch and once the children is found it updates it using the rest operator making a shallow copy only at that level and returns it , so basically all the other values will still refer to the original value but only the target value gets a new address.
I have only explained the process in a simplified manner in this blog , checkout my code on Github to see the full implementation, If you liked this blog and want to know the whole process of how I did the manipulation , please do let me know in the comments and I will make a detailed blog on that.
Thanks for reading this far...