All articlesJavaScript

Ace Your Next JavaScript Interview: Values, References, Coercion & Equality (Part 2) ✨

Learn the deeper concepts in JavaScript, such as values, references, coercion & equality (5 minutes)

Petar IvanovPetar Ivanov
6 min read
On this page

A few months ago, I shared an article about how to Ace Your Next JavaScript Interview (part 1), where I dug deeper into fundamental concepts like Scope, Hoisting, and Closures.

In today’s article, I’ll continue to demystify and simplify other JavaScript concepts, such as values, references, coercion & equality.

Every time I have a JavaScript interview, I review these notes to refresh my knowledge and prepare.

If you missed the previous article (part 1), check it out here:

Primitive Types & Pass-by-Value

JavaScript has seven basic data types: string, number, boolean, undefined, null, symbol, and object (everything else).

The first six are called primitives or primitive types.

When you assign or pass a primitive value, JavaScript makes a fresh new copy each time.

This means that it never links back to the original.

TypeScript
const price = 16;

let newPrice = price; // JS copies 16 into `newPrice`

newPrice += 1;

console.log(price === newPrice); // false

console.log(price); // 16

console.log(newPrice); // 17

The same rule applies when passed inside a function where primitives stay isolated:

TypeScript
function increasePrice(x) {
  x += 1;
}

increasePrice(price);

console.log(price); // it's still 16

Primitivesare passed by value, so you work only on copies, never on the original value.


Objects & Pass-by-Reference

Objects and arrays are stored as references.

Two objects with the same shape are not equal unless they point to the same piece of memory.

You can think of these references as “links” to some piece of data on the computer.

TypeScript
const a = { val: 16 };
const b = { val: 16 };

console.log(a === b);  // false

When we compare the objects, we refer to their references (“links”), checking whether the underlying data (memory) is the same or not.

This also means that if we change something in a it won’t reflected also in b.

TypeScript
const a = { val: 16 };
const b = { val: 16 };

a.val = 160;

console.log(a.val); // 160
console.log(b.val); // 16

On the other side, if we assign one object to another, it will copy only the reference.

TypeScript
const a = { val: 16 };
const b = a; // JS copies the reference, not the actual data

console.log(a === b);  // true

In this case, both a and b point to the same data (memory), so the variables are equal.

This also means that if we change something in a it will be reflected also in b.

TypeScript
const a = { val: 16 };
const b = a;

a.val = 160;

console.log(a.val); // 160
console.log(b.val); // 160

If we want to create a new object based on existing one, we can use the spread operator:

TypeScript
const original = { val: 16 };
const copy = { ...original };

copy.val = 12;

console.log(original.str, copy.str);  // 16, 12

⚠️ Keep in mind that spreading only duplicates the top level properties of the object. Nested objects will still share the same references.

This is called shallow copy.

TypeScript
const original = { stats: { val: 16 } };
const copy = { ...original };

copy.stats.val = 12;

console.log(original.stats.val);  // 12 (not 16)

If we want to a deep copy, we can use the structuredClone method or similar:

TypeScript
const original = { stats: { val: 16 } };
const copy = structuredClone(original);

copy.stats.val = 12;

console.log(original.stats.val); // 16

Coercion

JavaScript often “helps” you with converting types on the fly.

Sometimes this behavior can be handy but also confusing.

Converting a value from one type to another is often called “type casting,” when done explicitly, and “coercion” when done implicitly (forced by the rules of how a value is used).

For example, addition with a string turns the numbers intro strings:

Text
42 + '' // '42'
42 + '0' // '420'

However, subtraction forcer strings into numbers:

Text
'42' - 7 // 35

There’re some other quirks like null + 1 → 1, undefined + 1 → NaN, etc.

❗Pro Tip: Try to always use the same data types in logical operations, if you’re not sure what you’re doing or don’t want unexpected behaviors.

To make this happen, we can use explicit casts or “Boxing Wrappers”:

TypeScript
Number('42'); // 42
String(42); // '42'
Boolean(undefined); // false;

Logical Operators Return Values

The | | and && operators don’t always give you true or false.

The value produced will always be the value of one of the two original values.

TypeScript
const a = 42, b = 'Hi', c = null;

a || b // 42   (first truthy)
a && b // 'Hi' (both truthy, returns second)
a || c // 42
a && c // null
b || c // 'Hi'
b && c // null (first truthy, second falsy)

When you use the | | operator, if the first value casts to true, you will get that value returned.

Otherwise, you’ll always get the second value returned.

In the case of && operator, you’ll always get the second value if they’re both casted to true.

If the first one is coerced to false then you’ll get it’s value returned.

As this behavior might look a bit confusing, it powers quick defaults and use cases:

TypeScript
function greet(name) {
  console.log(`Hello, ${name || 'friend'}!`);
}

greet();  // Hello, friend!

// or in modern JavaScript projects, you might see default parameters assigned in the function signature

function greet(name = 'friend') {
  console.log(`Hello, ${name}!`);
}

greet();  // Hello, friend!

or even in React:

TypeScript
{Boolean(name) && <p>{name}</p>}

Equality (== vs. ===)

  • == checks for value equality after coercion
  • === checks for value equality without coercion
Text
42 == '42'  // true (
42 === '42' // false

❗Pro Tip: Use === by default to avoid unexpected behaviors.


📌 TL;DR

  • Primitives are copied by value; Objects share references.
  • Be careful with shallow vs. deep copies.
  • Coercion happens silently. Prefer explicit casting.
  • | | and && return one of their operands. It’s handy for defaults.
  • Prefer === over == to keep comparison predictable.

Related articles

Whenever you’re ready, here’s how I can help you:

  1. 1.

    The Conscious React: React architecture, design & clean code — 100+ production tips across 6 chapters, updated for React 19, plus 4 companion repos you can clone and run.

  2. 2.

    The Conscious Node: Node.js architecture, design & clean code — 157 production tips across 10 chapters, from module boundaries to the transactional outbox and zero-downtime deploys.

  3. 3.

    The JavaScript Architect Bundle: Both books + all React companion repos + CLAUDE.md rulesets + both playbooks. The complete path from developer to architect.

  4. 4.

    Free Resources: Architecture playbooks, cheat-sheets, and the JavaScript Architect Roadmap — practical guides for leveling up to senior.

The T-Shaped Dev

Join 30K+ engineers leveling up to architect

One practical tip on JavaScript, React, Node.js, and software architecture every week. No spam, unsubscribe anytime.

Petar Ivanov

Written by

Petar Ivanov

Software engineer, author, and speaker. I help JavaScript developers grow from Mid → Senior → Architect — production-grade React, Node.js, and AI systems.