Beginner0 questionsFull Guide

JavaScript Scope Interview Questions

Scope determines where variables are accessible. Master global, function, block scope and the scope chain for JavaScript interviews.

The Mental Model

Picture a set of nested rooms in a house. Each room has its own cabinet of variables. When you need something, you check your own cabinet first. Not there? Walk to the next outer room and check. Keep walking outward until you find it or exit the house entirely and get an error. That chain of rooms is scope. The innermost room is the current function. Each outer room is an enclosing function. The house itself is the global scope. The critical rule: you can always look outward, never inward. A parent room cannot see inside a child room. But a child can see everything in every room it's nested inside. Scope is determined at write time — where a function is written in the source code, not where it's called from. This is called lexical scoping.

The Explanation

The three types of scope

1. Global scope

Variables declared outside any function or block. In browsers, they become properties of window. Visible everywhere in your program.

var appName = 'JSPrep'

function anywhere() {
  console.log(appName)  // 'JSPrep' ✓ — visible from anywhere
}

2. Function scope

Variables declared inside a function exist only within that function, created when it runs, destroyed when it returns.

function makeGreeting() {
  var message = 'Hello'
  console.log(message)  // 'Hello' ✓
}

makeGreeting()
console.log(message)  // ReferenceError — doesn't exist here

3. Block scope

let and const are block-scoped — they exist only within the { } block they're declared in. var ignores block boundaries entirely.

if (true) {
  let blockVar = 'block-scoped'
  var funcVar  = 'function-scoped'
}

console.log(funcVar)   // 'function-scoped' — var leaked out
console.log(blockVar)  // ReferenceError — let stayed inside

The scope chain

When JavaScript looks up a variable, it searches the current scope first, then moves outward until it finds it or reaches global scope. If still not found: ReferenceError.

const global = 'global'

function outer() {
  const outerVar = 'outer'

  function inner() {
    const innerVar = 'inner'

    console.log(innerVar)   // found in inner ✓
    console.log(outerVar)   // not in inner → found in outer ✓
    console.log(global)     // not in outer → found in global ✓
    console.log(missing)    // not anywhere → ReferenceError ✗
  }

  console.log(innerVar)  // ReferenceError — can't look inward
}

Lexical scope — location of definition, not call

Scope is fixed at the point where the function is written. Calling it from a different location does not change what it can see.

const x = 'global x'

function readX() {
  console.log(x)  // always reads from where readX was DEFINED
}

function other() {
  const x = 'local x'
  readX()           // still prints 'global x', not 'local x'
}

other()  // 'global x'

Variable shadowing

When an inner scope declares a variable with the same name as an outer one, the inner version shadows the outer. The outer still exists — it's just unreachable from within.

let score = 100

function game() {
  let score = 50       // shadows outer score — different variable
  console.log(score)   // 50
}

game()
console.log(score)     // 100 — outer untouched

Classic bug: you intend to modify the outer variable but accidentally declare a new inner one instead. Use ESLint's no-shadow rule to catch this.

Module scope (ES modules)

Every ES module file has its own scope. Top-level variables are not global — only explicitly exported values are accessible to other modules.

// utils.js
const secret = 'internal'    // module-scoped only
export const PI = 3.14159    // accessible to importers

// main.js
import { PI } from './utils.js'
console.log(PI)      // 3.14159 ✓
console.log(secret)  // ReferenceError

IIFE — scope isolation before modules

Before ES modules, the Immediately Invoked Function Expression created private scope to avoid polluting globals. You'll see this pattern in legacy code.

(function() {
  var private = 'only exists in here'
  // all internal logic here
})()

console.log(private)  // ReferenceError — perfectly isolated

Common scope bugs

// Bug 1: Forgetting var/let/const creates a global
function setName() {
  name = 'Alice'   // no declaration — accidentally global in non-strict mode
}

// Bug 2: var leaks out of for loops
for (var i = 0; i < 3; i++) {}
console.log(i)  // 3 — leaked. Use let instead.

// Bug 3: let in a block, used outside it
if (condition) {
  let result = compute()
}
return result  // ReferenceError — result died with the block

Common Misconceptions

⚠️

Many devs think scope is determined by where a function is called — but actually JavaScript uses lexical scoping, where scope is determined by where a function is written. Calling from a different location doesn't change what variables it sees.

⚠️

Many devs think var is block-scoped like let and const — but actually var ignores block boundaries entirely. A var declared inside an if or for block leaks into the entire surrounding function. This is one of the core reasons let was introduced in ES6.

⚠️

Many devs think inner scopes can't affect outer scopes — but actually a closure can both read and write to outer scope variables. If an inner function assigns to an outer variable (not shadows — assigns), that mutation persists after the inner function returns.

⚠️

Many devs think top-level variables in a module file are global — but actually ES module files have their own scope. Only exported values cross the boundary. This is fundamentally different from script files without type="module", where top-level vars do become globals.

⚠️

Many devs think shadowed variables are the same variable — but actually they are completely independent. Changing the inner shadowed variable has zero effect on the outer one. They share a name, not storage.

⚠️

Many devs think strict mode changes scoping rules — but actually it doesn't change scope. What it does is throw a ReferenceError for undeclared variable assignments instead of silently creating a global. It makes scope bugs visible rather than hiding them.

Where You'll See This in Real Code

React's useState returns state scoped to each component instance — because each render call is a function invocation with its own scope, every component gets independent state without any global state management needed for basic use cases.

The "callback hell" pattern in legacy Node.js code was a scope and readability problem — deeply nested callbacks created deeply nested scopes, causing variable name collisions and making the scope chain hard to reason about, which is what Promises and async/await were designed to flatten.

Webpack's scope hoisting optimization analyzes the scope chain across your entire module graph at build time — it merges module scopes where safe to do so, reducing the number of function wrappers and improving runtime performance by up to 10%.

ESLint's no-shadow rule is enabled in virtually every serious production codebase — it was introduced because subtle shadowing bugs have shipped to production at major companies when a developer unknowingly redeclared a variable name that existed in an outer scope.

CSS Modules and CSS-in-JS exist because CSS has no scope — every class name is global, causing unpredictable style collisions at scale. The entire ecosystem of scoped styling tools exists to bring JavaScript-style scoping to stylesheets.

The var-in-for-loop bug affects setTimeout callbacks throughout legacy codebases — because var is function-scoped, the loop variable is shared across all iterations, so async callbacks capture the final value rather than the per-iteration value. Converting to let fixes it because let creates a new binding per iteration.

Interview Cheat Sheet

  • Global scope: accessible everywhere; function scope: inside function only; block scope: inside {} with let/const
  • var is function-scoped; let and const are block-scoped
  • The scope chain: JS looks outward from current scope until it finds the variable or reaches global
  • Lexical scope: scope is determined at write-time (where code is written), not runtime
  • Variable shadowing: inner scope variable hides outer scope variable with the same name
💡

How to Answer in an Interview

  • 1.Draw the nested scope boxes visually when explaining — interviewers appreciate the mental model
  • 2.Distinguish scope (variable accessibility) from context (the value of this)
  • 3.Be ready to trace a scope chain lookup step-by-step for a code snippet
📖 Deep Dive Articles
var vs let vs const in JavaScript: The Complete Comparison9 min readTop 50 JavaScript Interview Questions (With Deep Answers)18 min readJavaScript Scope Explained: Lexical Scope, Scope Chain, and How Variables Are Found10 min readJavaScript Hoisting Explained: What Actually Moves, What Doesn't, and Why It Matters9 min readJavaScript Prototypes Explained: The Object Model Behind Every Class and Method10 min readJavaScript Closures: What They Actually Are (And Why Interviews Love Them)10 min readJavaScript Output Questions: 30 Tricky Examples That Test Real Understanding14 min read

Practice Questions

No questions tagged to this topic yet.

Tag questions in Admin → Questions by setting the "Topic Page" field to javascript-scope-interview-questions.

Related Topics

JavaScript var, let, and const Interview Questions
Beginner·3–5 Qs
JavaScript Hoisting Interview Questions
Intermediate·4–8 Qs
JavaScript Closure Interview Questions
Intermediate·8–12 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz