Difference Between var, let, and const in JavaScript: Complete Technical Guide
The primary difference between var, let, and const in JavaScript is that var declarations are function-scoped and hoisted with undefined initialization, while let and const are block-scoped with temporal dead zone restrictions, with const additionally preventing reassignment of the variable binding.
Understanding the difference between var, let, and const in JavaScript is essential for writing predictable, maintainable code in modern applications. These three declaration keywords exhibit distinct behaviors regarding scope, hoisting, and mutability, concepts thoroughly documented in the h5bp/Front-end-Developer-Interview-Questions repository. This guide examines the technical implementation details, practical code examples, and specific source file references that demonstrate these fundamental JavaScript concepts.
Scope Differences: Function vs. Block Scoping
var and Function Scope
Variables declared with var are function-scoped, meaning they are accessible throughout the entire function body in which they are declared, regardless of block boundaries. If declared outside any function, var creates a property on the global object (window in browsers). This behavior can lead to unintended variable leakage across blocks.
let and const Block Scope
Both let and const are block-scoped, limiting variable visibility to the nearest enclosing curly braces {}, including if statements, for loops, and standalone blocks. This prevents variables from leaking outside their intended context and reduces naming collisions in complex codebases.
Hoisting and the Temporal Dead Zone
var Hoisting Behavior
Variables declared with var are hoisted to the top of their function scope and automatically initialized with undefined. This allows referencing the variable before its declaration line without throwing a ReferenceError, though the value will be undefined until the assignment executes.
let and const Temporal Dead Zone
While let and const declarations are also hoisted, they remain uninitialized in the temporal dead zone (TDZ) from the start of the block until the declaration line executes. Accessing the variable during this period throws a ReferenceError, preventing the use of uninitialized variables.
Redeclaration and Reassignment Rules
Redeclaration Behavior
var: Can be redeclared in the same scope without error, potentially masking previous values unintentionally.let: Cannot be redeclared in the same block scope; attempting to do so throws a SyntaxError.const: Same aslet—redeclaration in the same block throws a SyntaxError.
Reassignment and Mutability
varandlet: Can be reassigned arbitrarily throughout their scope.const: Cannot be reassigned once initialized; attempting to do so throws a TypeError. However, if the value is an object or array, its properties or elements can still be mutated.
const obj = { key: 'value' };
obj.key = 'new'; // Works: mutating the object
// obj = {}; // TypeError: Assignment to constant variable
For true deep immutability, use Object.freeze():
const frozen = Object.freeze({ a: 1 });
frozen.a = 2; // Silently fails (or throws in strict mode)
Practical Code Examples
Function Scope vs. Block Scope
// ---- var (function-scoped) ----
function varDemo() {
if (true) {
var x = 1; // x is visible throughout the whole function
}
console.log(x); // 1 (no block restriction)
console.log(y); // undefined – y is hoisted
var y = 2;
}
varDemo();
// ---- let (block-scoped) ----
function letDemo() {
if (true) {
let a = 1;
console.log(a); // OK: 1
}
// console.log(a); // ReferenceError: a is not defined
}
letDemo();
Temporal Dead Zone Demonstration
// var hoisting
console.log(varVar); // undefined (no error)
var varVar = 'I am var';
// let temporal dead zone
// console.log(letVar); // ReferenceError: Cannot access 'letVar' before initialization
let letVar = 'I am let';
const Binding Immutability
const C = 10;
// C = 11; // TypeError: Assignment to constant variable.
// Object mutation with const
const config = { apiUrl: 'https://api.example.com' };
config.apiUrl = 'https://new.example.com'; // Valid mutation
// config = {}; // TypeError
// True immutability with Object.freeze()
const settings = Object.freeze({ timeout: 5000 });
settings.timeout = 3000; // Silently fails (or throws in strict mode)
Source Code Reference
The conceptual distinctions between var, let, and const are documented in the h5bp/Front-end-Developer-Interview-Questions repository, specifically within:
src/questions/javascript-questions.md– Contains the interview question list, including the entry "What are the differences between variables created usinglet,varorconst?" at lines 43-45.src/_data/questions.json– JSON representation of all questions used by the site generator to render the UI.src/_includes/assets/js/app.js– The site's JavaScript implementation, demonstrating modern practices such as usingletandconstovervarin real-world application code.
These files illustrate both the theoretical examination of JavaScript variable declarations and their practical application in modern front-end development.
Summary
varis function-scoped, hoisted withundefinedinitialization, allows redeclaration in the same scope, and creates properties on the global object when used outside functions.letis block-scoped, hoisted but uninitialized (temporal dead zone), forbids redeclaration in the same scope, and allows reassignment.constshares scoping and hoisting rules withletbut creates an immutable binding that prevents reassignment; however, object and array contents remain mutable unless frozen withObject.freeze().- Modern JavaScript best practices favor
constby default, usingletonly when reassignment is necessary, and avoidingvarentirely to prevent scope leakage and hoisting confusion.
Frequently Asked Questions
What happens if I try to redeclare a let or const variable in the same scope?
Attempting to redeclare a let or const variable in the same block scope throws a SyntaxError. Unlike var, which silently allows redeclaration within the same function scope, let and const enforce unique declarations within their block to prevent accidental variable masking and reduce bugs in complex codebases.
Can I modify the contents of a const array or object?
Yes, you can mutate the properties of objects or elements of arrays declared with const, but you cannot reassign the variable binding itself. The const keyword guarantees immutability of the binding, not the value. For deep immutability where object properties cannot be modified, use Object.freeze() to prevent mutations to object properties.
Why does accessing a let variable before its declaration throw a ReferenceError while var returns undefined?
This occurs due to the temporal dead zone (TDZ). While both var and let are hoisted to the top of their scope, var is automatically initialized with undefined. In contrast, let (and const) remain uninitialized until the declaration line executes, making any access beforehand illegal and throwing a ReferenceError to prevent usage of uninitialized variables.
When should I use var instead of let or const in modern JavaScript?
You should rarely use var in modern JavaScript. The only potential use case is when specifically targeting legacy environments that do not support ES6 (ECMAScript 2015), or when maintaining older codebases that rely on function-scoping behavior. Modern best practices recommend using const by default for immutable bindings, let when reassignment is necessary, and avoiding var entirely to prevent function-scoping leaks and hoisting confusion.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →