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.

JavaScripting
JavaScripting Team
·5 min read

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.

JavaScript
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.
JavaScript
let a = 1, b = 2, c = 3; console.log(a, b, c);
JavaScript
// 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.

JavaScript
const PI = 3.14159; // PI = 3; // Uncommenting would throw a TypeError let count = 0; count += 1; console.log(count);
JavaScript
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.

JavaScript
console.log(foo); // undefined (hoisted by var) var foo = 1;
JavaScript
// TDZ example // ReferenceError: Cannot access 'bar' before initialization console.log(bar); let bar = 2;
JavaScript
// 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.

JavaScript
function greet(name) { let greeting = "Hello"; const message = `${greeting}, ${name}!`; return message; } console.log(greet("Alice"));
JavaScript
if (true) { let inside = "block"; console.log(inside); } // console.log(inside); // ReferenceError: inside is not defined
JavaScript
// 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()); // 2

These 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.

JavaScript
"use strict"; // Attempting implicit global // undeclare = 5; // ReferenceError: undeclare is not defined
JavaScript
let value = 10; function f() { let value = 20; // shadows outer value return value; } console.log(f(), value); // 20 10
JavaScript
let a = 5; // let a = 6; // SyntaxError: Identifier 'a' has already been declared

To 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.

JavaScript
const person = { name: "Sara", age: 30 }; const { name, age } = person; console.log(name, age);
JavaScript
const { a = 1, b = 2 } = {}; console.log(a, b);
JavaScript
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.

JavaScript
console.log(typeof window); // browser: 'object', Node: 'undefined'
JavaScript
// 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.

Related Articles