Declaring JavaScript Variables: Let, Const, and Var in Practice
Learn how to declare JavaScript variables using let, const, and var. Master scope, hoisting, and modern best practices with clear examples.
Declaring Variables in JavaScript: Basics and Best Practices
If you’re wondering how do you declare a javascript variable, the answer starts with three keywords: let, const, and var. In modern JavaScript, let and const cover most use cases, while var is primarily for legacy scripts. Variables are dynamically typed, which means their type can change. However, the binding itself—whether it’s block-scoped or function-scoped—is determined by the keyword you choose. In this section, we’ll establish the mental model and show simple, concrete examples to illustrate scope and assignment.
let x = 5;
const y = 10;
var z = 3;
// Print values to confirm bindings
console.log(x, y, z);- Notes:
- Block scope with let/const means variables disappear when exiting the block.
- var is function-scoped, which can leak outside blocks.
let a = 1, b = 2, c = 3;
console.log(a, b, c);// Dynamic typing demo
let d = 42;
d = "forty-two"; // Type can change at runtime
console.log(d);Choosing Between let, const, and var
Modern JavaScript development favors let and const because they provide clearer intent and safer scoping. Use const for bindings you never reassign and let for values that will change. Reserve var for legacy code or situations where function scope is explicitly needed. This choice affects how your code behaves in loops, blocks, and functions, and it reduces surprises in refactors.
const PI = 3.14159;
// PI = 3; // Uncommenting would throw a TypeError
let count = 0;
count += 1;
console.log(count);function testVar() {
if (true) {
var x = 'hello';
}
return x;
}
console.log(testVar()); // 'hello'Choosing the right keyword helps convey intent and prevents accidental reassignments or scope leaks.
Temporal Dead Zone and Hoisting Explained
Hoisting determines how declarations are processed by the JavaScript engine. var declarations are hoisted and initialized with undefined, which can mask bugs. Let and const are also hoisted, but they remain in a temporal dead zone (TDZ) until their initialization runs, meaning accessing them before initialization throws a ReferenceError. Understanding TDZ and hoisting helps you predict runtime behavior.
console.log(foo); // undefined (hoisted by var)
var foo = 1;// TDZ example
// ReferenceError: Cannot access 'bar' before initialization
console.log(bar);
let bar = 2;// TDZ in functions
function show() {
console.log(baz); // ReferenceError
let baz = 5;
}
show();Tip: Always declare variables at the top of their scope only when it improves readability; otherwise, place declarations close to their first use to reduce confusion.
Practical Examples: Declaring Variables in Functions and Blocks
Functions and blocks illustrate how scope affects variable visibility. This section demonstrates common patterns you’ll encounter in real code, including closures and block-scoped declarations.
function greet(name) {
let greeting = "Hello";
const message = `${greeting}, ${name}!`;
return message;
}
console.log(greet("Alice"));if (true) {
let inside = "block";
console.log(inside);
}
// console.log(inside); // ReferenceError: inside is not defined// Closure example with let
function counter() {
let i = 0;
return function() {
i += 1;
return i;
};
}
const next = counter();
console.log(next()); // 1
console.log(next()); // 2These patterns help you manage state safely within functions and blocks, reducing accidental mutations and keeping behavior predictable.
Common Pitfalls and How to Avoid Them
JavaScript variable declaration is easy to misuse when you’re not mindful of scope and strict mode. Common mistakes include creating implicit globals, shadowing outer bindings, and re-declaring the same variable with let or const. Enabling strict mode (or using modules) helps catch these errors early.
"use strict";
// Attempting implicit global
// undeclare = 5; // ReferenceError: undeclare is not definedlet value = 10;
function f() {
let value = 20; // shadows outer value
return value;
}
console.log(f(), value); // 20 10let a = 5;
// let a = 6; // SyntaxError: Identifier 'a' has already been declaredTo avoid surprises, prefer const by default, minimize global scope, and avoid re-declaring variables in the same scope.
Destructuring and Defaults for Cleaner Declarations
Destructuring lets you extract multiple values from objects or arrays into distinct bindings in a single, readable statement. This reduces boilerplate and improves readability, especially when handling API responses or complex data.
const person = { name: "Sara", age: 30 };
const { name, age } = person;
console.log(name, age);const { a = 1, b = 2 } = {};
console.log(a, b);let [first, second] = [1, 2];
console.log(first, second);Destructuring works with defaults, nested structures, and can be combined with rest syntax for flexible patterns.
Environment Nuances: Node vs Browser and Version Guidance
When declaring variables in different environments, note subtle differences in globals and modules. Browsers expose window as the global object, whereas Node uses global. In modern environments, modules execute in their own scope, which affects how variables are declared and accessed.
console.log(typeof window); // browser: 'object', Node: 'undefined'// ES module example (browser/Node)
export let modVar = 42;In practice, write portable code by avoiding implicit globals, using strict mode, and preferring block-scoped declarations. If you target older browsers, consider transpilation (e.g., Babel) to align with modern syntax while preserving compatibility.
