Skip to content
Home
JavaScript Variables: var, let, and const Explained

JavaScript Variables: var, let, and const Explained

JavaScript JavaScript 9 min read 1824 words Intermediate ExcellentWiki Editorial Team

Variables are the foundation of any JavaScript program. They store data values — numbers, strings, objects, arrays, or functions — and give you a way to reference that data throughout your code. How you declare a variable determines where it lives in memory, how long it lives, and whether other parts of your program can access it.

Modern JavaScript gives you three keywords to declare variables: var, let, and const. Each behaves differently when it comes to scope, hoisting, reassignment, and the temporal dead zone. Understanding these differences is essential for writing predictable, bug-free code. This guide covers everything you need to know about JavaScript variables and the best practices used by professional developers.

Variable Declarations: var, let, and const

The three declaration keywords each serve a distinct purpose. var is the legacy keyword from the original JavaScript specification and behaves in ways that often lead to subtle bugs. let and const were introduced in ES6 (2015) and fix most of var’s design flaws.

var name = "Alice";        // function-scoped, hoisted, can be redeclared
let age = 30;              // block-scoped, hoisted (TDZ), can be reassigned
const birthYear = 1994;    // block-scoped, hoisted (TDZ), cannot be reassigned

var — The Legacy Declaration

var has function scope rather than block scope. If you declare a var inside a function, it belongs to that function. If you declare it outside any function (at the top level), it becomes a property of the global object (window in browsers, global in Node.js). This global leakage is one of the reasons var is discouraged.

function example() {
  var x = 1;
  if (true) {
    var x = 2;  // same variable! No block scope
    console.log(x); // 2
  }
  console.log(x); // 2 (the if block didn't create a new scope)
---

Another quirk: var declarations are hoisted to the top of their function scope, initialized with undefined. This means you can reference a var variable before its declaration line without getting an error — you just get undefined.

console.log(a); // undefined (not an error!)
var a = 5;

This behavior, while intentional, causes confusion and is the primary reason the language introduced let and const.

let — The Modern Replacement

let is the modern replacement for var for variables that need to be reassigned. It has block scope, meaning it exists only within the nearest pair of curly braces {}.

let x = 1;
if (true) {
  let x = 2;  // different variable (block-scoped)
  console.log(x); // 2
---
console.log(x); // 1 (outer x is untouched)

let is also hoisted, but unlike var, it is not initialized. It enters a temporal dead zone (TDZ) from the start of the block until the declaration is encountered. Accessing the variable in the TDZ throws a ReferenceError.

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;

The TDZ prevents the kind of accidental undefined access that var allows, making bugs easier to catch during development.

const — The Immutable Binding

const works identically to let in terms of scope and the temporal dead zone. The difference: you must assign a value at declaration, and you cannot reassign the binding.

const pi = 3.14159;
pi = 3;  // TypeError: Assignment to constant variable

A common misconception is that const makes the value itself immutable. It does not. const only prevents reassignment of the variable name. If the value is an object or array, its contents can still be modified.

const person = { name: "Alice" };
person.name = "Bob";   // allowed — mutating the object is not reassignment
person = {};           // TypeError — reassigning the variable is forbidden

const numbers = [1, 2, 3];
numbers.push(4);       // allowed
numbers = [5, 6, 7];   // TypeError

Use const by default for any variable that should not be reassigned. It communicates intent clearly and prevents accidental overwrites.

Scope Rules

Scope determines where a variable is accessible in your code. JavaScript has three levels of scope.

Global Scope

Variables declared outside any function or block belong to the global scope. They are accessible from anywhere in the program, including inside functions and blocks. Global variables are problematic because any part of your program can modify them, making the flow of data hard to trace.

const globalVar = "I'm everywhere";
function test() {
  console.log(globalVar); // "I'm everywhere"
---

In browsers, global var declarations also create properties on the window object, which can collide with built-in browser APIs. Modern code minimizes global variables, often relying on module systems (ES modules or CommonJS) to encapsulate data.

Function Scope

Variables declared with var inside a function are scoped to that function. They cannot be accessed from outside. This is the only scope that var respects — it ignores blocks like if, for, and while.

function greet() {
  var message = "Hello";
  console.log(message); // "Hello"
---
console.log(message); // ReferenceError: message is not defined

Block Scope

Variables declared with let and const inside a block {} are only accessible within that block. This includes function bodies, if blocks, for loops, and while loops.

{
  let blockScoped = "only in this block";
  const alsoBlockScoped = "same here";
  var notBlockScoped = "leaks out";
---
console.log(notBlockScoped); // "leaks out"
console.log(blockScoped);    // ReferenceError

Block scope makes loops behave more predictably. With var in a loop, the same variable is shared across all iterations. With let, each iteration gets its own binding.

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

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

Hoisting

Hoisting is JavaScript’s behavior of moving declarations to the top of their scope before code execution. The mechanism applies differently to each keyword.

var Hoisting

var declarations are hoisted to the top of their function scope and initialized with undefined. The assignment stays in place.

console.log(x); // undefined (hoisted declaration)
var x = 5;
// Internally becomes:
// var x;
// console.log(x);
// x = 5;

let and const Hoisting

let and const declarations are also hoisted, but they are not initialized. They enter the temporal dead zone from the start of the block until the declaration is evaluated.

{
  console.log(y); // ReferenceError
  let y = 5;
---

The hoisting of let and const is important to understand because it means typeof checks can also throw errors.

typeof undeclaredVar;    // "undefined" (no error)
typeof declaredLater;    // ReferenceError
let declaredLater = 5;

Naming Conventions

JavaScript has established naming conventions that help code readability and prevent errors.

// camelCase for variables, functions, and methods
const userName = "Alice";
let itemCount = 0;
function fetchData() {}

// UPPER_SNAKE_CASE for true constants (known at authoring time)
const MAX_RETRIES = 3;
const API_BASE_URL = "https://api.example.com";

// _private convention (not enforced by the language)
const _internalCache = new Map();

Identifiers can contain letters, digits, $, and _, but cannot start with a digit. JavaScript is case-sensitive, so myVar and myvar are different variables.

Best Practices

Prefer const by Default

Use const for every variable that will not be reassigned. This covers most declarations in modern JavaScript, especially when working with objects and arrays where you mutate properties but not the binding itself.

const config = { theme: "dark", lang: "en" };
config.theme = "light";  // mutation is fine

If you need to reassign, use let. Avoid var entirely in new code — there is no situation where var offers an advantage over let or const in ES6+ code.

Minimize Scope

Keep variables as close to their usage as possible. Declare variables inside the smallest enclosing block rather than hoisting them to the top of the function.

// Good
function process(items) {
  const filtered = items.filter(/* ... */);
  // filtered is only needed here
---

// Less good
function process(items) {
  let filtered;
  // 20 lines of unrelated code
  filtered = items.filter(/* ... */);
---

Avoid Implicit Globals

Assigning to an undeclared variable creates a property on the global object. Enable strict mode with "use strict" to make this an error.

"use strict";
function setValue() {
  x = 10;  // ReferenceError in strict mode
---

Destructure for Clarity

When working with objects and arrays, destructuring makes your variable bindings explicit and reduces repetition.

const user = { id: 1, name: "Alice", email: "alice@example.com" };

// Instead of:
const id = user.id;
const name = user.name;

// Use destructuring:
const { id, name } = user;

// With arrays:
const [first, second] = [10, 20, 30];

Common Pitfalls

// Pitfall 1: Redeclaring var
var x = 1;
var x = 2;  // No error — silently overwrites

// Pitfall 2: Forgetting const requires initializer
const z;    // SyntaxError: Missing initializer in const declaration

// Pitfall 3: Confusing const immutability with value immutability
const obj = { a: 1 };
obj.a = 2;              // Works (mutation)
obj = { a: 2 };         // TypeError (reassignment)

// Pitfall 4: Temporal dead zone surprises
function test() {
  console.log(typeof value); // ReferenceError (not "undefined")
  let value = 5;
---

Summary

Featurevarletconst
ScopeFunctionBlockBlock
HoistedYes, initialized undefinedYes, uninitialized (TDZ)Yes, uninitialized (TDZ)
RedeclarableYesNoNo
ReassignableYesYesNo
Must initializeNoNoYes

Modern JavaScript code should use const by default, let when reassignment is needed, and never var. This pattern produces more predictable code by eliminating accidental redeclarations, preventing reassignment mistakes, and containing variables within their intended scope. Understanding how hoisting and the temporal dead zone work will help you debug the rare cases where variable access behaves unexpectedly.

See also: JavaScript DOM Manipulation: Complete Guide.

See also: JavaScript Modules: A Complete Guide.

Frequently Asked Questions

What is the minimum system requirement for javascript variables?

System requirements vary by implementation. Most modern solutions require at least 4GB of RAM, a multi-core processor, and a stable internet connection. For specific applications, refer to the vendor documentation. Hardware requirements typically increase with scale — enterprise deployments need significantly more resources than personal or small business setups.

How does this compare to alternative approaches?

Every technology choice involves trade-offs. Some prioritize ease of use over customization, while others offer maximum control at the cost of complexity. Evaluating your specific needs, technical expertise, and growth plans helps determine the right fit. Many organizations use a combination of approaches to balance competing priorities.

What security considerations should I be aware of?

Security should be considered from the start, not as an afterthought. Keep all software updated, use strong authentication, encrypt sensitive data, and follow the principle of least privilege. Regular security audits and staying informed about emerging threats are essential practices for maintaining a secure deployment.

How do I troubleshoot common issues?

Start by isolating the problem: check logs, verify configurations, and test components individually. Common issues include network connectivity problems, permission errors, and version incompatibilities. Systematic troubleshooting — changing one variable at a time — helps identify root causes efficiently. Online communities and documentation are valuable resources when you encounter unfamiliar problems.

Section: JavaScript 1824 words 9 min read Intermediate 756 articles in section Report inaccuracy Back to top