Skip to content

L60 - Memoización

La memoización es una técnica de optimización que consiste en cachear los resultados de funciones costosas para evitar recalcularlos cuando se invocan con los mismos argumentos. Es una aplicación práctica de closures y programación funcional.

function memoizar(fn) {
const cache = {}; // Closure: el cache persiste entre llamadas
return function(arg) {
if (cache[arg] !== undefined) {
console.log(`Cache hit: ${arg}${cache[arg]}`);
return cache[arg];
}
console.log(`Calculando: ${arg}`);
const resultado = fn(arg);
cache[arg] = resultado;
return resultado;
};
}
// Función costosa (simulada)
function lenta(numero) {
// Simular operación pesada
for (let i = 0; i < 1e7; i++) {}
return numero * 2;
}
const rapida = memoizar(lenta);
console.log(rapida(5)); // Calcula y cachea
console.log(rapida(5)); // Cache hit, instantáneo
console.log(rapida(10)); // Calcula y cachea
console.log(rapida(5)); // Cache hit
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
console.time('fib(40)');
console.log(fib(40)); // ~1 segundo
console.timeEnd('fib(40)');

El problema es que fib(5) llama a fib(4) y fib(3), que a su vez llaman a los mismos subproblemas múltiples veces. El árbol de llamadas crece exponencialmente.

function fibMemoizada(n, cache = {}) {
if (cache[n] !== undefined) return cache[n];
if (n <= 1) return n;
cache[n] = fibMemoizada(n - 1, cache) + fibMemoizada(n - 2, cache);
return cache[n];
}
console.time('fibMemo(1000)');
console.log(fibMemoizada(1000)); // Instantáneo (4.3e208)
console.timeEnd('fibMemo(1000)');

De O(2ⁿ) a O(n). La diferencia es astronómica: fib(100) con memoización es trivial, sin ella requeriría más cálculos que átomos en el universo.

function memoizar(fn) {
const cache = new Map();
return function(...args) {
const clave = JSON.stringify(args); // Serializar argumentos múltiples
if (cache.has(clave)) {
console.log(`Cache hit: ${clave}`);
return cache.get(clave);
}
console.log(`Calculando: ${clave}`);
const resultado = fn.apply(this, args);
cache.set(clave, resultado);
return resultado;
};
}
const fibMemo = memoizar(function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
});
console.log(fibMemo(50)); // 12586269025
function crearCalculadora() {
const cache = new Map();
return {
factorial(n) {
if (cache.has(n)) return cache.get(n);
if (n <= 1) return 1;
const resultado = n * this.factorial(n - 1);
cache.set(n, resultado);
return resultado;
},
limpiarCache() {
cache.clear();
},
tamanoCache() {
return cache.size;
}
};
}
const calc = crearCalculadora();
console.log(calc.factorial(10)); // 3628800
console.log(calc.factorial(10)); // Desde cache
console.log(calc.tamanoCache()); // 1 (solo guardó el resultado de 10)

🧠 Memoización con WeakMap (para objetos)

Section titled “🧠 Memoización con WeakMap (para objetos)”

Cuando los argumentos son objetos, usar Map puede causar memory leaks porque las referencias nunca se liberan. WeakMap permite que los objetos sean recolectados:

function memoizarConWeakMap(fn) {
const cache = new WeakMap();
return function(obj) {
if (cache.has(obj)) return cache.get(obj);
const resultado = fn(obj);
cache.set(obj, resultado);
return resultado;
};
}
function procesoPesado(data) {
// Simular procesamiento costoso
return { ...data, procesado: true, timestamp: Date.now() };
}
const memoPesado = memoizarConWeakMap(procesoPesado);
const obj = { id: 1, nombre: 'test' };
console.log(memoPesado(obj)); // Calcula
console.log(memoPesado(obj)); // Cache
// Si obj se vuelve null, WeakMap permite que se libere la memoria
  1. Componentes React: React.memo, useMemo, useCallback.
  2. Peticiones API: cachear respuestas para evitar llamadas repetidas.
  3. Procesamiento de datos: transformaciones pesadas sobre los mismos datos.
  4. Algoritmos recursivos: Fibonacci, torres de Hanoi, rutas en grafos.
  5. Renderizado: evitar volver a calcular estilos o layouts.
  • Funciones impuras: si el resultado depende de algo externo (fecha, random, input del usuario).
  • Funciones que se llaman una sola vez: el overhead de cachear no compensa.
  • Argumentos muy variados: si casi nunca se repiten argumentos, el cache será inútil.
  • Funciones muy rápidas: el overhead de Map.set/get puede ser mayor que recalcular.
// ❌ No memoizar: función impura
function obtenerFecha() {
return new Date().toISOString();
}
// ❌ No memoizar: función con efectos secundarios
function enviarPeticion(url) {
return fetch(url); // Cada llamada debe ser real
}

🎯 Regla de oro: memoiza solo funciones puras, costosas y con argumentos repetidos. Si no cumple las tres, probablemente no vale la pena.