Skip to content
← Back to Blog
JavaScript

WTF Is Immutability in JavaScript? ¿WTF Es La Inmutabilidad en JavaScript?

March 10, 2026 10 de marzo de 2026

WTF Is Immutability in JavaScript?

You’ve heard it in code reviews: “don’t mutate the state.” You’ve seen it in React docs, Angular best practices, functional programming guides. But what does it actually mean — and why does it matter so much?

Mutation: The Root of the Problem

Mutation means changing something that already exists in memory.

const user = { name: 'Francisco', role: 'dev' }
user.role = 'lead' // mutation

You took the object that existed and changed it in place. Simple? Yes. Dangerous? Absolutely — especially in large codebases.

Why Mutation Causes Bugs

Unexpected Side Effects

function promote(user) {
  user.role = 'lead' // mutates the original
  return user
}

const dev = { name: 'Francisco', role: 'dev' }
const lead = promote(dev)

console.log(dev.role)  // 'lead' — you didn't want this
console.log(lead.role) // 'lead'

You called promote and got back a lead — but you also silently changed dev. Two variables, one broken reality.

Change Detection Breaks in Frameworks

Angular’s OnPush and React’s React.memo compare references. If you mutate an object, the reference stays the same — the framework thinks nothing changed and doesn’t re-render.

// This breaks OnPush / React.memo
const items = []
items.push(newItem) // same reference, mutation detected = never

// This works
const items = [...oldItems, newItem] // new reference, change detected = always

Immutability: Never Change, Always Replace

The immutable approach is simple: instead of modifying something, you create a new thing with the change applied.

const user = { name: 'Francisco', role: 'dev' }

// Immutable update
const promoted = { ...user, role: 'lead' }

console.log(user.role)     // 'dev' — original untouched
console.log(promoted.role) // 'lead'

Immutable Array Patterns

OperationMutable ❌Immutable ✅
Add itemarr.push(x)[...arr, x]
Remove itemarr.splice(i, 1)arr.filter((_, idx) => idx !== i)
Update itemarr[i] = xarr.map((item, idx) => idx === i ? x : item)
Sortarr.sort()[...arr].sort()
Reversearr.reverse()[...arr].reverse()

Object.freeze — Enforced Immutability

If you want JavaScript to actually prevent mutation:

const config = Object.freeze({
  apiUrl: 'https://api.example.com',
  timeout: 5000,
})

config.timeout = 10000 // silently ignored in sloppy mode, throws in strict mode
console.log(config.timeout) // 5000

Note: freeze is shallow. Nested objects are still mutable. Use structuredClone + freeze for deep immutability, or a library like immer.

Immer: Immutability With Mutable Syntax

When your updates are complex, spread notation gets ugly fast. immer lets you write mutation-style code that produces immutable results:

import { produce } from 'immer'

const state = { users: [{ id: 1, name: 'Francisco', active: true }] }

const nextState = produce(state, (draft) => {
  draft.users[0].active = false // looks like mutation, isn't
})

console.log(state.users[0].active)     // true — original intact
console.log(nextState.users[0].active) // false

immer is used internally by Redux Toolkit. If you’re using NgRx, the same patterns apply.

The Rule

Simple to state, hard to internalize:

Never modify. Always return something new.

It sounds like extra work. It’s actually less work — because you stop debugging why something changed when you didn’t expect it to. Predictable state is worth every extra spread operator.

¿WTF Es La Inmutabilidad en JavaScript?

Lo escuchaste en code reviews: “no mutes el estado.” Lo viste en la documentación de React, en las buenas prácticas de Angular, en guías de programación funcional. Pero ¿qué significa realmente — y por qué importa tanto?

Mutación: La Raíz del Problema

Mutación significa cambiar algo que ya existe en memoria.

const user = { name: 'Francisco', role: 'dev' }
user.role = 'lead' // mutación

Tomaste el objeto que existía y lo cambiaste en el lugar. ¿Simple? Sí. ¿Peligroso? Absolutamente — especialmente en codebases grandes.

Por Qué La Mutación Genera Bugs

Efectos Secundarios Inesperados

function promote(user) {
  user.role = 'lead' // muta el original
  return user
}

const dev = { name: 'Francisco', role: 'dev' }
const lead = promote(dev)

console.log(dev.role)  // 'lead' — no querías esto
console.log(lead.role) // 'lead'

Llamaste a promote y obtuviste un lead — pero también cambiaste silenciosamente dev. Dos variables, una realidad rota.

La Detección de Cambios Se Rompe en Frameworks

El OnPush de Angular y el React.memo de React comparan referencias. Si mutas un objeto, la referencia queda igual — el framework cree que nada cambió y no re-renderiza.

// Esto rompe OnPush / React.memo
const items = []
items.push(newItem) // misma referencia, nunca se detecta el cambio

// Esto funciona
const items = [...oldItems, newItem] // nueva referencia, cambio detectado siempre

Inmutabilidad: Nunca Cambiar, Siempre Reemplazar

El enfoque inmutable es simple: en vez de modificar algo, creas algo nuevo con el cambio aplicado.

const user = { name: 'Francisco', role: 'dev' }

// Actualización inmutable
const promoted = { ...user, role: 'lead' }

console.log(user.role)     // 'dev' — original intacto
console.log(promoted.role) // 'lead'

Patrones Inmutables para Arrays

OperaciónMutable ❌Inmutable ✅
Agregar elementoarr.push(x)[...arr, x]
Eliminar elementoarr.splice(i, 1)arr.filter((_, idx) => idx !== i)
Actualizar elementoarr[i] = xarr.map((item, idx) => idx === i ? x : item)
Ordenararr.sort()[...arr].sort()
Invertirarr.reverse()[...arr].reverse()

Object.freeze — Inmutabilidad Forzada

Si quieres que JavaScript realmente prevenga la mutación:

const config = Object.freeze({
  apiUrl: 'https://api.example.com',
  timeout: 5000,
})

config.timeout = 10000 // ignorado silenciosamente en modo normal, error en strict mode
console.log(config.timeout) // 5000

Nota: freeze es superficial. Los objetos anidados siguen siendo mutables. Usa structuredClone + freeze para inmutabilidad profunda, o una librería como immer.

Immer: Inmutabilidad con Sintaxis Mutable

Cuando tus actualizaciones son complejas, la notación de spread se vuelve horrible. immer te permite escribir código de estilo mutación que produce resultados inmutables:

import { produce } from 'immer'

const state = { users: [{ id: 1, name: 'Francisco', active: true }] }

const nextState = produce(state, (draft) => {
  draft.users[0].active = false // parece mutación, no lo es
})

console.log(state.users[0].active)     // true — original intacto
console.log(nextState.users[0].active) // false

immer se usa internamente en Redux Toolkit. Si usas NgRx, los mismos patrones aplican.

La Regla

Simple de enunciar, difícil de internalizar:

Nunca modificar. Siempre devolver algo nuevo.

Suena a más trabajo. En realidad es menos trabajo — porque dejas de debuggear por qué algo cambió cuando no lo esperabas. El estado predecible vale cada spread operator extra.

← Back to Blog