Riad Kilani
  • Bio
  • Portfolio
  • Blog
  • Contact
  • Accessibility
  • Case Studies
  • CSS
  • Design
  • Front-End
  • HTML
  • JavaScript
  • News
  • Productivity
  • Random Thoughts
  • SEO
  • Themes
  • Trends
  • Tutorials
  • TypeScript
  • TypeSCript From The Ground Up
  • UX Engineering
  • Web Development
  • Wordpress
Home » JavaScript » Understanding JavaScript Scope: A Beginner’s Guide

Understanding JavaScript Scope: A Beginner’s Guide

August 6, 2025
understanding-javascript-scope

Scope is just a fancy word for where your variables and functions can be seen. Think of your program like a house: some things live in the living room (everyone can see them), and some things live in a bedroom drawer (only visible inside that room). Once you get this idea, a lot of weird JavaScript bugs suddenly make sense.


What “scope” really means

When JavaScript tries to use a name (like count or user), it looks around the current “room,” then the rooms outside it, and so on, until it finds that name. If it can’t find it anywhere, you get a ReferenceError.


The four scopes you’ll meet most

1) Global scope — “everyone can see this”

Anything declared outside functions/blocks is global.

let siteName = "Example"; // global

function show() {
  console.log(siteName); // "Example"
}
show();

Why care? Globals are convenient but risky—easy to overwrite and hard to track in bigger apps.


2) Function scope — “private to this function”

Variables declared inside a function stay inside that function.

function demo() {
  const secret = "shh";
  var legacy = "function-scoped";
  let counter = 0;
}
console.log(typeof secret);  // "undefined"
console.log(typeof legacy);  // "undefined"
console.log(typeof counter); // "undefined"

3) Block scope (let/const) — “inside these braces only”

Blocks like if, for, { ... } make their own scope for let and const (but not for var).

if (true) {
  let a = 1;
  const b = 2;
  var c = 3; // NOT block-scoped
}
console.log(typeof a); // "undefined"
console.log(typeof b); // "undefined"
console.log(c);        // 3  // leaked because var is function-scoped

Modern rule of thumb: use const by default, let when you need to reassign, and avoid var.


4) Module scope — “isolated file”

Each ES module (each .js file using import/export) has its own top-level scope. Nothing becomes global by accident.

// mathUtils.js
export const PI = 3.14159;
export function area(r) { return PI * r * r; }

// main.js
import { area } from "./mathUtils.js";
console.log(area(2)); // 12.56636

Lexical scope — it’s about where you write it, not how you call it

JavaScript is lexically scoped. Inner code can see outer variables because of where the code is written.

const outside = "I am outside";

function parent() {
  const msg = "Hello";
  function child() {
    console.log(msg, outside); // "Hello I am outside"
  }
  child();
}
parent();

Closures — keeping access after a function returns

A closure happens when a function remembers variables from the place it was created—even after that outer function finishes.

function makeCounter(start = 0) {
  let count = start;
  return function () {
    count += 1;
    return count;
  };
}

const next = makeCounter(5);
console.log(next()); // 6
console.log(next()); // 7
console.log(next()); // 8

Real-life use: event handlers

function attachTracker(button, label) {
  let clicks = 0;
  button.addEventListener("click", () => {
    clicks++;
    console.log(`${label} clicked ${clicks} times`);
  });
}

attachTracker(document.querySelector("#saveBtn"), "Save");

Hoisting & the Temporal Dead Zone (TDZ) — the “before it exists” problem

  • var is hoisted and initialized to undefined.
  • let and const are hoisted but not initialized. Touching them too early throws a ReferenceError. That early window is the TDZ.
  • Function declarations are hoisted with their bodies.
console.log(x); // undefined (var hoisted + initialized)
var x = 10;

try {
  console.log(y); // ReferenceError (TDZ)
} catch (e) {
  console.log("TDZ for y:", e.message);
}
let y = 20;

greet(); // OK (function declaration is hoisted)
function greet() {
  console.log("Hi!");
}

try {
  speak(); // TypeError: speak is not a function
} catch (e) {
  console.log(e.message);
}
const speak = function () { console.log("Hello"); };

Shadowing — same name, closer wins

If you redeclare a name in an inner scope, it shadows the outer one.

const user = "Riad";

function show() {
  const user = "Admin"; // shadows outer 'user'
  console.log(user);    // "Admin"
}
show();
console.log(user);      // "Riad"

Shadowing isn’t “bad,” but it can confuse readers. Prefer clearer names.


Loops + closures — use let to capture the right value

let creates a new variable per loop iteration, which plays nicely with callbacks.

// Using var (buggy)
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log("var i:", i), 0);
}
// => "var i: 3" three times

// Using let (correct)
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log("let j:", j), 0);
}
// => 0, 1, 2

Old trick: IIFEs (Immediately Invoked Function Expressions)

Before let/const, we used IIFEs to make a temporary private scope.

(function () {
  const temp = "private";
  console.log("Inside IIFE:", temp);
})();
console.log(typeof temp); // "undefined"

Strict mode — stop accidental globals

"use strict" makes JavaScript stricter (in a good way). One big win: it prevents creating globals by forgetting let/const/var.

"use strict";

function setName() {
  name = "Riad"; // ReferenceError in strict mode
}
setName();

Common gotchas (and easy fixes)

1) Accidental global

function go() {
  thing = 123; // ❌ creates a global in sloppy mode
}
// ✅ Fix
function goSafe() {
  const thing = 123;
}

2) var leaking out of blocks

if (true) { var leaked = 1; }
console.log(leaked); // 1

// ✅ Prefer let/const
if (true) { let safe = 1; }
// console.log(safe); // ReferenceError

3) Shadowing that hides intent

const total = 100;
function calc(total) { // shadows outer 'total'
  return total + 20;
}
// ✅ Rename parameters to be clear
function calcFixed(baseTotal) {
  return baseTotal + 20;
}

Quick FAQ

Is this part of scope?
No—this depends on how a function is called. Scope depends on where code is written. Arrow functions grab this from the surrounding scope.

Does catch (err) create its own scope?
Yes. The err is scoped to that catch block only.

Are classes in strict mode?
Yes. Class bodies are strict by default.


Tiny practice questions

  1. What happens here and why?
console.log(a);
let a = 5;
  1. Fix the closure bug in the loop:
const buttons = document.querySelectorAll("button");
for (var i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener("click", () => {
    console.log("Clicked index:", i);
  });
}
// ✅ Use let
  1. Predict the output:
(function() {
  console.log(typeof x); // ?
  if (true) {
    let x = 1;
  }
})();

Wrap-up

Scope controls where your variables live and who can use them. Master the differences between global, function, block, and module scope—plus closures, hoisting, and the TDZ—and your JavaScript becomes more predictable and easier to debug.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Next Post → Exploring New Features in CSS: What You Need to Know in 2025

Categories

  • Accessibility
  • Case Studies
  • CSS
  • Design
  • Front-End
  • HTML
  • JavaScript
  • News
  • Productivity
  • Random Thoughts
  • SEO
  • Themes
  • Trends
  • Tutorials
  • TypeScript
  • TypeSCript From The Ground Up
  • UX Engineering
  • Web Development
  • Wordpress

Recent Posts

  • Native CSS Is Quietly Replacing Sass, But It Isn’t Replacing the “Need” for Sass
  • Everyday Types Explained (From the Ground Up)
  • 2026 CSS Features You Must Know (Shipped Late 2025–Now)
  • 60 JavaScript Projects in 60 Days
  • JavaScript vs TypeScript: What Actually Changes

Tags

accessibility accessible web design ADA compliance async components Career Journey cascade layers code splitting composables composition api computed properties container queries css Design Inspiration Design Systems disability access File Organization Front-End Development Frontend frontend development immutability javascript JavaScript reducers lazy loading Material Design Modern CSS performance Personal Growth react React useReducer Redux Resume screen readers seo Suspense Teleport TypeScript UI/UX UI Engineering UX UX Engineering Vue Router WCAG web accessibility Web Development Web Performance

Riad Kilani Front-End Developer

© 2026 Riad Kilani. All rights reserved.