En JavaScript, let, const y var son las tres formas de declarar variables. Cada una tiene reglas distintas de ámbito, redeclaración, reasignación y comportamiento durante la fase de hoisting. Conocer estas diferencias evita errores comunes y ayuda a escribir código más predecible. Aunque let, const y var cumplen el mismo propósito básico, sus características influyen directamente en cómo se comporta el código, especialmente en bucles, cierres y objetos.

let y const se introdujeron en ECMAScript 2015 (ES6). Antes solo existía var. Hoy la práctica recomendada es usar const por defecto y let solo cuando necesites reasignar el valor de la variable. Evitar var en código nuevo reduce sorpresas relacionadas con el ámbito y el hoisting.

Resumen rápido

Característica var let const
Ámbito función (o global) bloque bloque
Redeclaración en el mismo ámbito permitida no permitida no permitida
Reasignación permitida permitida no permitida
Hoisting / zona muerta temporal se eleva e inicializa con undefined se eleva pero entra en zona muerta temporal (ReferenceError) se eleva pero entra en zona muerta temporal (ReferenceError)
Propiedad del objeto global sí (en scripts clásicos) no no

Ámbito de función y de bloque

var tiene ámbito de función. Una variable declarada con var dentro de una función es visible en toda esa función, incluso antes de su declaración. Fuera de funciones, var crea una variable global.

function ejemploVar() {
  if (true) {
    var mensaje = "Hola";
  }
  console.log(mensaje); // "Hola" - visible fuera del if
}

ejemploVar();

let y const tienen ámbito de bloque. Solo son visibles dentro del bloque donde se declaran (entre llaves {}). Un bloque puede ser un if, un for, un while o simplemente un par de llaves.

function ejemploLet() {
  if (true) {
    let mensaje = "Hola";
    console.log(mensaje); // "Hola"
  }
  // console.log(mensaje); // ReferenceError: mensaje is not defined
}

ejemploLet();

Este comportamiento de bloque es especialmente útil cuando quieres limitar la visibilidad de una variable a un pequeño fragmento de código.

Hoisting y zona muerta temporal

JavaScript mueve las declaraciones al principio de su ámbito antes de ejecutar el código. Esto se llama hoisting.

Con var, la variable se eleva y se inicializa automáticamente con undefined. Por eso puedes leerla antes de declararla, aunque el valor será undefined.

console.log(edad); // undefined
var edad = 30;
console.log(edad); // 30

let y const también se elevan, pero no se inicializan. Están en lo que se conoce como zona muerta temporal (Temporal Dead Zone). Intentar usarlas antes de su declaración lanza un ReferenceError.

console.log(nombre); // ReferenceError: Cannot access 'nombre' before initialization
let nombre = "Ana";

console.log(edad); // ReferenceError
const edad = 25;

Este error ayuda a detectar usos prematuros de variables y evita comportamientos silenciosos difíciles de depurar.

const no hace inmutable el valor

const impide reasignar la variable, es decir, no puedes hacer const x = 5; x = 6;. Pero si la variable contiene un objeto o un array, sí puedes modificar su contenido.

const persona = {
  nombre: "Lucía",
  edad: 28
};

persona.edad = 29; // Esto funciona
console.log(persona.edad); // 29

// persona = {}; // TypeError: Assignment to constant variable.

Si quieres evitar también la modificación de las propiedades de primer nivel, puedes usar Object.freeze():

const personaInmutable = Object.freeze({
  nombre: "Lucía",
  edad: 28
});

personaInmutable.edad = 30; // En modo estricto lanza TypeError. En modo no estricto falla silenciosamente.
console.log(personaInmutable.edad); // 28

Recuerda que Object.freeze() solo congela el nivel superficial. Para objetos profundamente anidados necesitarías una función de freeze recursivo o una librería.

El clásico problema del bucle

Uno de los errores más comunes con var aparece cuando se usa dentro de un bucle junto con funciones asíncronas como setTimeout.

for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 100);
}
// Salida: 3 3 3

Todas las funciones capturan la misma variable i, que al terminar el bucle vale 3.

Usando let el problema desaparece porque cada iteración crea una nueva variable i con ámbito de bloque.

for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 100);
}
// Salida: 0 1 2

Este ejemplo muestra claramente por qué let es la opción más segura en bucles.

Qué usar hoy

La regla más extendida en la comunidad es: usa const por defecto. Solo cambia a let cuando necesites reasignar el valor de la variable (por ejemplo, en contadores, acumuladores o cuando actualizas una referencia). Evita var en código nuevo.

Esta práctica hace el código más legible porque la declaración indica la intención: si ves const, sabes que esa variable nunca será reasignada. Además reduces la superficie de errores relacionados con hoisting y ámbito.

En proyectos modernos que usan módulos (import/export), las variables declaradas con let y const no se filtran al objeto global, lo que mejora el aislamiento entre archivos.

Si estás empezando, te recomendamos leer primero qué es JavaScript y cómo empezar y luego explorar más tutoriales de JavaScript para reforzar estos conceptos con ejemplos prácticos.

Para información oficial y más ejemplos puedes consultar la documentación de let en MDN y const en MDN.

¿Puedo usar const con arrays y objetos?

Sí. Puedes modificar el contenido del array o las propiedades del objeto. Solo no puedes reasignar la variable para que apunte a otro array u objeto distinto.

¿Qué pasa si declaro una variable con let en un bloque y luego intento redeclararla con var en el mismo ámbito?

Lanza un error de sintaxis. let y var no pueden redeclarar la misma variable en el mismo ámbito.

¿Siguen siendo útiles los closures con let?

Por supuesto. let respeta el ámbito de bloque, por lo que cada iteración o cada ejecución de función crea su propio cierre con su propia copia de la variable, lo que resuelve muchos problemas clásicos que teníamos con var.

Imagen de portada: Lorenzo Cafaro, CC0, vía Wikimedia Commons (recortada).