Values vs References in JavaScript
This is the bug you’ve had a hundred times but couldn’t name. You copy an object, change a property, and suddenly the original changed too. Or you pass an array to a function and it comes back different. Welcome to the value vs reference problem.
Two Categories of Types
JavaScript has two categories of types, and they behave completely differently in memory.
Primitive types — stored by value:
string,number,boolean,null,undefined,symbol,bigint
Reference types — stored by reference:
object,array,function
Primitives: Copied by Value
When you assign a primitive, JavaScript copies the actual value.
let a = 10
let b = a
b = 20
console.log(a) // 10 — unchanged
console.log(b) // 20
b got its own copy. Changing b has zero effect on a. Clean, predictable.
Objects: Copied by Reference
When you assign an object, JavaScript copies the reference — a pointer to the same place in memory.
const user = { name: 'Francisco', age: 29 }
const copy = user
copy.age = 30
console.log(user.age) // 30 — you changed the original
console.log(copy.age) // 30
copy and user point to the exact same object. This is the root of countless bugs.
The Same Trap with Arrays
const original = [1, 2, 3]
const copy = original
copy.push(4)
console.log(original) // [1, 2, 3, 4] — mutated
How to Actually Copy
Shallow Copy — Spread Operator
const user = { name: 'Francisco', role: 'dev' }
const copy = { ...user }
copy.role = 'lead'
console.log(user.role) // 'dev' — safe
console.log(copy.role) // 'lead'
For arrays:
const original = [1, 2, 3]
const copy = [...original]
copy.push(4)
console.log(original) // [1, 2, 3] — untouched
The Shallow Copy Trap
Spread only goes one level deep. If your object has nested objects, the nested references are still shared.
const user = { name: 'Francisco', address: { city: 'Houston' } }
const copy = { ...user }
copy.address.city = 'Monterrey'
console.log(user.address.city) // 'Monterrey' — still mutated!
Deep Copy — structuredClone
For deeply nested structures, use the native structuredClone:
const user = { name: 'Francisco', address: { city: 'Houston' } }
const copy = structuredClone(user)
copy.address.city = 'Monterrey'
console.log(user.address.city) // 'Houston' — safe
structuredClone is available in all modern browsers and Node.js 17+. No library needed.
Functions and References
When you pass an object to a function, you’re passing the reference. The function can mutate the original.
function birthday(person) {
person.age += 1 // mutates the original
}
const user = { name: 'Francisco', age: 29 }
birthday(user)
console.log(user.age) // 30
If you don’t want that, spread inside the function:
function birthday(person) {
return { ...person, age: person.age + 1 }
}
const user = { name: 'Francisco', age: 29 }
const older = birthday(user)
console.log(user.age) // 29 — untouched
console.log(older.age) // 30
The Mental Model
Think of primitives as the value written on a piece of paper. Objects are a piece of paper with an address written on it — multiple people can have the same address, and if you change what’s at that address, everyone sees it.
Once this clicks, a whole class of bugs becomes obvious before you even run the code.
Valores vs Referencias en JavaScript
Este es el bug que tuviste mil veces pero no podías nombrar. Copias un objeto, cambias una propiedad, y de repente el original también cambió. O pasas un array a una función y vuelve diferente. Bienvenido al problema de valores vs referencias.
Dos Categorías de Tipos
JavaScript tiene dos categorías de tipos, y se comportan de manera completamente diferente en memoria.
Tipos primitivos — guardados por valor:
string,number,boolean,null,undefined,symbol,bigint
Tipos de referencia — guardados por referencia:
object,array,function
Primitivos: Copiados por Valor
Cuando asignas un primitivo, JavaScript copia el valor real.
let a = 10
let b = a
b = 20
console.log(a) // 10 — sin cambios
console.log(b) // 20
b tiene su propia copia. Cambiar b no tiene ningún efecto en a. Limpio, predecible.
Objetos: Copiados por Referencia
Cuando asignas un objeto, JavaScript copia la referencia — un puntero al mismo lugar en memoria.
const user = { name: 'Francisco', age: 29 }
const copy = user
copy.age = 30
console.log(user.age) // 30 — modificaste el original
console.log(copy.age) // 30
copy y user apuntan exactamente al mismo objeto. Aquí está la raíz de incontables bugs.
La Misma Trampa con Arrays
const original = [1, 2, 3]
const copy = original
copy.push(4)
console.log(original) // [1, 2, 3, 4] — mutado
Cómo Copiar de Verdad
Copia Superficial — Spread Operator
const user = { name: 'Francisco', role: 'dev' }
const copy = { ...user }
copy.role = 'lead'
console.log(user.role) // 'dev' — seguro
console.log(copy.role) // 'lead'
Para arrays:
const original = [1, 2, 3]
const copy = [...original]
copy.push(4)
console.log(original) // [1, 2, 3] — intacto
La Trampa de la Copia Superficial
El spread solo va un nivel de profundidad. Si tu objeto tiene objetos anidados, las referencias anidadas siguen siendo compartidas.
const user = { name: 'Francisco', address: { city: 'Houston' } }
const copy = { ...user }
copy.address.city = 'Monterrey'
console.log(user.address.city) // 'Monterrey' — igual se muteó!
Copia Profunda — structuredClone
Para estructuras profundamente anidadas, usa el nativo structuredClone:
const user = { name: 'Francisco', address: { city: 'Houston' } }
const copy = structuredClone(user)
copy.address.city = 'Monterrey'
console.log(user.address.city) // 'Houston' — seguro
structuredClone está disponible en todos los navegadores modernos y Node.js 17+. Sin librerías.
Funciones y Referencias
Cuando pasas un objeto a una función, estás pasando la referencia. La función puede mutar el original.
function birthday(person) {
person.age += 1 // muta el original
}
const user = { name: 'Francisco', age: 29 }
birthday(user)
console.log(user.age) // 30
Si no quieres eso, haz spread dentro de la función:
function birthday(person) {
return { ...person, age: person.age + 1 }
}
const user = { name: 'Francisco', age: 29 }
const older = birthday(user)
console.log(user.age) // 29 — intacto
console.log(older.age) // 30
El Modelo Mental
Piensa en los primitivos como el valor escrito en un papel. Los objetos son un papel con una dirección escrita — varias personas pueden tener la misma dirección, y si cambias lo que hay ahí, todos lo ven.
Cuando esto hace clic, toda una clase de bugs se vuelve obvia antes de correr el código.