← All posts

Programming 3 min read

JavaScript Under the Hood: Primitive Values vs. Reference Types

Understanding how memory allocation, value comparison, and object references work under the hood in JavaScript.

JavaScript Under the Hood: Primitive Values vs. Reference Types

Have you ever compared two identical-looking objects in JavaScript, expecting true, only to be met with false?


let name = "Luke";
let otherName = "Luke";
console.log(name === otherName); // true

let person = { name: "Luke" };
let otherPerson = { name: "Luke" };
console.log(person === otherPerson); // false

Both objects contain the exact same key and value, so why does string comparison evaluate to true while object comparison fails? The answer comes down to how JavaScript handles primitives vs. non-primitives (reference types) under the hood.


Primitives vs. Objects: What’s the Difference?

In JavaScript, data types fall into two main categories:

  • Primitives: string, number, boolean, null, undefined, symbol, and bigint.

  • Non-Primitives (Objects): Object, Array, Function, Date, etc.

JavaScript


// Primitives
let name = "Luke";
let age = 18;
let isMale = true;

// Non-Primitives
let person = { name: "Luke" };
let ages = [1, 2, 3];
const sayHello = () => {};

How Comparison Works Under the Hood

1. Primitives Are Compared by Value

When comparing primitive types, JavaScript checks the actual data stored inside the variables.

Think of primitive variables as two name tags. If both tags read "Luke", JavaScript considers them equal because the text matches character-for-character.

2. Objects Are Compared by Reference (Memory Address)

When comparing non-primitive types, JavaScript does not look at the properties inside the object. Instead, it checks whether both variables point to the exact same location in memory.

JavaScript


let person = { name: "Luke" };      // Stored at Memory Address A101
let otherPerson = { name: "Luke" }; // Stored at Memory Address A102

console.log(person === otherPerson); // Asking: Is A101 === A102? (false)

Analogy: Imagine two separate houses located at 101 Main St and 102 Main St. In both houses, a person named Luke lives inside. When JavaScript compares person === otherPerson, it asks, "Are these two the exact same street address?" It doesn't care that the occupants look identical—the addresses are different, so the comparison returns false.


Why Does JavaScript Work This Way?

Performance. Objects in JavaScript can be deeply nested and massive. Checking every nested key-value pair every time you perform a === check would be extremely slow and resource-intensive. Checking whether two variables share the same memory pointer takes instant, constant time.


How to Perform Deep Equality Checks

To compare two distinct objects by their actual properties rather than their reference, you must perform a shallow or deep comparison.

Here is a basic shallow equality function:

JavaScript


const areObjectsEqual = (obj1, obj2) => {
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);

  // Must have the same number of keys
  if (keys1.length !== keys2.length) return false;

  for (let key of keys1) {
    // Must contain the same key and value
    if (!Object.prototype.hasOwnProperty.call(obj2, key)) return false;
    if (obj1[key] !== obj2[key]) return false;
  }

  return true;
};

// Testing the function:
const person1 = { height: 6.7, weight: 250, name: "John" };
const person2 = person1; // Same reference
const person3 = { height: 6.7, weight: 250, name: "John" }; // Different reference, same content

// Reference checks
console.log(person1 === person2); // true (points to the same object)
console.log(person1 === person3); // false (different memory references)

// Shallow equality check
console.log(areObjectsEqual(person1, person3)); // true (contents match)

Key Takeaway

  • Primitives are compared by value (what they contain).

  • Objects are compared by reference (where they live in memory).