Objects and References: What’s Really Going On?


Have you ever heard people say “pass by reference” or “pass by value” in programming discussions? I’m sure you have. Maybe you nodded along, thinking you had a rough idea — and maybe you did.
But have you ever paused to truly understand what a "reference" actually is?
Let’s break it down. At first, a variable might seem like a simple container — you assign it a value, and it holds that value. But the moment you create an object and assign it to a variable, things start to feel different. That same variable, which once stored a simple
int
orchar
, now becomes a reference to an object in memory.It no longer holds the object itself, but rather points to its memory address — like a name tag pointing to someone in a crowd. This small shift changes everything.
Now whenever you pass a reference to any of the method you are not passing the object itself, but passing the reference to the object. That means the method can modify the original object, not just a copy of it.
So, let’s dive deep and really understand what’s actually going on under the hood.
Why Objects?
In the world of programming, our main goal is to solve real-world problems using code. To do that, we need a way to translate real-world entities into something our code can understand and work with — and this is exactly where objects come into play.
Objects allow us to represent real-world entities in code. They are defined by their properties (data) and behaviors (functions or methods), just like real-world things. For example, a Car
object in code can have properties like color
, model
, and speed
, and behaviors like drive()
or brake()
. This makes it easier to model, manage, and solve real-life problems in a structured, logical way.
In simple terms:
👉 Objects act as digital representations of real-world things, helping us bridge the gap between reality and programming.
What are objects?
A good way to think about objects is as structured data, somewhat like familiar data structures such as linked lists, arrays, or hash maps — but with a big difference: objects can hold both data and the functions that operate on that data.
A data structure is a way to organize and store data in memory so it can be accessed and modified efficiently.
Primarily, there are two types of data structures:
1. Primitive Data Structures (Language supports out of the box{ex: array})
2. Non-Primitive Data Structures(Designed by the programmer using primitive
DataStructure{ex: Stack, Graphs})
Objects store data in key-value pairs (especially in languages like JavaScript or Python dictionaries), but in strict OOP (like Java or C++), these are actually fields or attributes. Or specifically, Instances of a Class
const remote={
company:"Amazon",
Dimensions:{
length:20,
width:30
},
volumeup:()=>{
console.log("volume up")
},
volumedown:()=>{
console.log("volume down")
},
compatibility:["Fire tv","Jio tv","airtel xtreme"]
}
→ Here every attribute like company,Dimensions… Are considered as keys while the corresponding data or functions are values.
→ Now let Take some Example with Strings:
let p1="Kamal"
let p2=fNmae
console.log(p1)
console.log(p2)
/* output
->Kamal
->Kamal
*/
p2="John"
console.log(p1)
console.log(p2)
/* output
->Kamal
->John
*/
→When p2
is changed, p1
is not affected. This means the value of p1
is simply copied to p2
. Any change to p1
does not affect p2
, and vice versa.
→ Lets take Another Example with objects:
let p1={
fname"Kamal"
}
let p2=p1
console.log(p1)
console.log(p2)
/* output
->{fname:"Kamal"}
->{fname:"Kamal"}
*/
p2.fname="john"
console.log(p1)
console.log(p2)
/* output
->{fname:"john"}
->{fname:"john"}
*/
→ As we can see, when we change the value using p2
, the original object referenced by p1
is also affected.
This happens because p1
is a reference to the object { fname: "Kamal" }
, and when we do p2 = p1
, we're not creating a new object — we're simply assigning the same reference to p2
. So both p1
and p2
now point to the same object in memory.
But how is that possible?
If p1
and p2
behave like references for objects, why don’t they behave the same way with strings? In the case of strings, it seems like they just copy values — when we do p1 = p2
, changes to one don't affect the other.
So why does this happen with objects but not with strings?
To understand this, we need to look at how data is stored in memory during execution — specifically, the difference between primitive types (like strings, numbers, booleans) and reference types (like objects and arrays).
Behind the Scenes: Memory & References
→ To understand this topic we need to know how this primitive dataStructures and nonPrimitive data structure are placed inside the memory
→ Lets start with variables:
A variable is simply a named storage location in your program — a way to store data so you can use it later.
→ Now in any language the , when the program runs the memory is typically divided into two areas:
-
→Stores primitive values and variable references. It's fast and handles things like
let age = 20;
→It’s fast and automatically managed (follows a Last-In-First-Out model).
→Example:let age = 20;
→ the value20
is stored directly in the stack. -
→Stores objects, arrays, and non-primitive values — anything with complex structure
→More flexible but slower than stack memory..
→Variables in the stack hold references (pointers) to the data stored here.
→ All the variables are stored inside the stack memory and when the scope for the variable ends the variable is popped out of it
→ The =
(assignment) operator simply assigns a value. That’s it. So when you change p3
, you're just giving it a new value — it doesn’t affect any other variable that may have been previously assigned.
→Now, if every primitive type can be directly stored in the stack, you might wonder — why do we need heap memory at all?
The answer is: the stack has limited size and cannot grow dynamically. It’s meant for short-lived, fixed-size data like primitive values and references.
However, dynamic data structures — like objects, arrays, lists, or graphs — can grow and shrink at runtime. Since the stack doesn’t support this kind of flexible allocation, we use the heap to store such structures.
→ Lets declare a Object say, const P1={fname:”kamal”};
now lets see through diagrams how this actually stored in memory.
→ When an object is declared, the variable (like p1
) is created in the stack, while the actual object is stored in the heap.
The stack variable holds the address (reference) of the object in the heap. That’s why p1
is said to “refer” to the object.
→ When we declare const p2 = p1
, the compiler copies the reference (i.e., memory address) stored in p1
and assigns it to p2
.
So now, both p1
and p2
point to the same object in the heap — they are not separate objects.
This is why any change made using p2
will also be reflected when accessed via p1
— because both are referencing the same memory location. They act like aliases to the same object, not independent variables with their own values.
Concepts Behind Garbage Collector and Memory Leaks:
I honestly don’t understand why people make this simple topic so complicated — it’s actually easy to grasp once you get the idea.
→ Let’s say we have two references, p1
and p2
, both pointing to the same object:
const p1={ fname: "Kamal" }
const p2=p1;
This object is stored in the heap, while the references live in the stack.
Now, suppose both p1
and p2
go out of scope — meaning they’re no longer accessible. At this point, the object in the heap becomes an orphan — it no longer has any reference pointing to it. This kind of object is what we call unreachable.
Modern programming languages like Java, JavaScript, and Python come with built-in garbage collectors. These garbage collectors automatically detect unreachable (orphaned) objects and remove them from memory to free up space — preventing memory leaks.
Memory Leaks: Now let’s say the object in the heap is no longer needed, but we accidentally keep references to it through p1
or p2
. Because the object is still "reachable," the garbage collector won’t delete it — even though our program will never use it again.
This is what we call a memory leak — memory that could be freed but isn’t, because the program is holding on to it unnecessarily.
For Example:
function outer() {
let bigData = new Array(1000000); // a big array
return function inner() {
console.log("Still here");
};
}
const leak = outer();
If you want to read more about Garbage collectors, Memory leaks, and Shallow vs. Deep copies, click on the given links.
🙌 Thanks for Reading!
If this helped you understand memory better, share it with someone who’s also learning!
Have questions or want me to cover something deeper like closures, deep vs shallow copy, or manual memory management?
Drop a comment — I’d love to continue the conversation!
Subscribe to my newsletter
Read articles from Kamal Lochan Dash directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
