DEV Community

Tsedey Terefe
Tsedey Terefe

Posted on

JavaScript Core Fundamentals: var, let, const, Scope, and Closures

Understanding how JavaScript handles variables is crucial for mastering the language. This post walks through the differences between var, let, and const, the concept of scope, variable mutability, and concludes with an exploration of closures.


1. var vs let vs const: What’s the Difference?

Global Scope Behavior

When declared outside of any function or block, all three var, let, and const are globally accessible. However, their behavior differs:

  • var: Function-scoped or globally scoped if declared outside any function. It is hoisted and initialized with undefined.
  • let: Block-scoped. It is hoisted but not initialized, leading to a "Temporal Dead Zone" (TDZ) until the declaration is encountered.
  • const: Block-scoped. Like let, it is hoisted but not initialized. Additionally, it must be assigned a value at the time of declaration and cannot be reassigned later.

2. Understanding Scope

Scope determines the accessibility of variables. JavaScript has three types of scope:

  • Global Scope: Variables declared outside any function or block.
  • Function Scope: Variables declared within a function.
  • Block Scope: Variables declared within a block (e.g., inside {}).

Global Scope Example:

let myPodcastName = "ExtendWomenInTech";

function getPodcastName() {
  console.log(myPodcastName); // ✅ "ExtendWomenInTech"
}

getPodcastName();
console.log(myPodcastName);   // ✅ "ExtendWomenInTech"
Enter fullscreen mode Exit fullscreen mode

Function Scope Example:

function getPodcastName() {
  let myPodcastName = "ExtendWomenInTech";
  console.log(myPodcastName); // ✅ "ExtendWomenInTech"
}`

getPodcastName();
console.log(myPodcastName);   // ❌ ReferenceError: myPodcastName is not defined
Enter fullscreen mode Exit fullscreen mode

Block Scope Example:

{
  let topic = "Learning Scope";
  console.log(topic); // ✅ "Learning Scope"
}

console.log(topic); // ❌ ReferenceError: topic is not defined
Enter fullscreen mode Exit fullscreen mode

3. Assignment & Re-assignment

  • let and var allow both reassignment and mutation of variables.
  • const prevents reassignment of the variable itself but allows mutation of the value if it's an object.
let message = "Hello outside";

function showMessage() {
  let message = "Hello inside";
  console.log(message); // ✅ "Hello inside"

  message = "Hello changed inside";
  console.log(message); // ✅ "Hello changed inside"
}

showMessage();
console.log(message);   // ✅ "Hello outside"
Enter fullscreen mode Exit fullscreen mode

Object Mutation with const:

const user = { name: "Alice" };

function updateUser() {
  user.name = "Tsedey"; // ✅ Mutating property
  return user.name;
}

console.log(updateUser()); // ✅ "Tsedey"
console.log(user);         // ✅ { name: "Tsedey" }
Enter fullscreen mode Exit fullscreen mode

Attempting to reassign the entire object will result in an error:

// ❌ TypeError: Assignment to constant variable.
// user = { name: "Charlie" };
Enter fullscreen mode Exit fullscreen mode

4. var vs let vs const in Re-assignment

var nameVar = "Alice";
let nameLet = "Bob";
const nameConst = "Carol";

function updateNames() {
  nameVar = "Updated by var";   // ✅ works
  nameLet = "Updated by let";   // ✅ works
  // nameConst = "Updated by const"; // ❌ TypeError
}

updateNames();

console.log(nameVar); // ✅ "Updated by var"
console.log(nameLet); // ✅ "Updated by let"
console.log(nameConst); // ✅ "Carol"
Enter fullscreen mode Exit fullscreen mode

5. Scopes, Shadowing & Re-assignment

var globalVar = "I am global var";
let globalLet = "I am global let";

function scopeTest() {
  var funcVar = "I am func var";
  let funcLet = "I am func let";

  console.log(globalVar); // ✅ Accessible
  console.log(globalLet); // ✅ Accessible

  {
    var blockVar = "I am block var"; // function-scoped
    let blockLet = "I am block let"; // block-scoped

    console.log(funcVar); // ✅ Accessible
    console.log(funcLet); // ✅ Accessible
    console.log(blockVar); // ✅ Accessible
    console.log(blockLet); // ✅ Accessible

    blockLet = "Changed block let";
    console.log(blockLet); // ✅ "Changed block let"
  }

  console.log(blockVar); // ✅ Accessible
  // console.log(blockLet); // ❌ ReferenceError
}

scopeTest();

console.log(globalVar); // ✅ "I am global var"
console.log(globalLet); // ✅ "I am global let"
Enter fullscreen mode Exit fullscreen mode

6. Hoisting: The Sneaky Part

Hoisting refers to JavaScript's behavior of moving declarations to the top of their containing scope during the compile phase.

console.log(a); // ✅ undefined
var a = 10;

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

console.log(c); // ❌ ReferenceError: Cannot access 'c' before initialization
const c = 30;
Enter fullscreen mode Exit fullscreen mode
  • var: Hoisted and initialized with undefined.
  • let and const: Hoisted but not initialized, leading to a Temporal Dead Zone (TDZ) until the declaration is encountered.

7.Closures: Functions with Memory

A closure is a function that "remembers" its lexical scope, even when the function is executed outside that scope.

function outer() {
  let count = 0;
  return function inner() {
    count++;
    console.log(count);
  };
}

const counter = outer();
counter(); // ✅ 1
counter(); // ✅ 2
counter(); // ✅ 3
Enter fullscreen mode Exit fullscreen mode

In this example, the inner function forms a closure that retains access to the count variable from its lexical scope, even after the outer function has finished executing.

💬 Final Thought

Understanding how scope, reassignment, and hoisting work under the hood will make debugging and structuring your code far easier. When in doubt, use const by default, switch to let when you need reassignment, and avoid var unless you're debugging legacy code.


Top comments (0)