Lesson 10 of 14

Lesson 10 — Scope in JavaScript

Title: Global Scope, Function Scope, and Block Scope

Description: Scope defines where variables can be accessed in code. JavaScript has global scope, function scope, and block scope ({} with let and const). Variables are resolved through a scope chain, where inner scopes can access outer ones. This lesson also covers shadowing, where inner variables override outer ones with the same name.

Why it matters for QA: Scope mistakes can lead to shared or leaked data between tests, unexpected reuse of variables, or functions capturing the wrong values. Proper scope usage keeps test data isolated and makes test execution predictable and stable.


1. What scope means

Scope defines the area in code where a variable (identifier) can be accessed without getting a ReferenceError.

javascriptjavascript
const testName = "login";

function printTestName() {
  console.log(testName);
}

printTestName();

printTestName can access testName because of lexical environment. Inner functions can read variables from outer scopes where they were defined.


2. Global scope

A variable declared outside of functions and blocks is in the global scope for that file or script.

javascriptjavascript
const baseUrl = "https://example.com";

function buildLoginUrl() {
  return `${baseUrl}/login`;
}

console.log(buildLoginUrl());

True module-level constants (immutable configuration, base URLs) legitimately live in outer scope.

Avoid mutable globals that carry scenario state:

javascriptjavascript
let currentUserEmail = "";

function rememberUser(email) {
  currentUserEmail = email;
}

3. Function scope

Variables declared inside a function are available only inside that function.

javascriptjavascript
function createLoginMessage(email) {
  const message = `Logging in as ${email}`;
  return message;
}

console.log(createLoginMessage("qa@example.com"));

// console.log(message); // ReferenceError

This is good. The helper keeps its internal details private.


4. Function parameters are local too

Parameters live inside the function scope.

javascriptjavascript
function maskEmail(email) {
  const [name, domain] = email.split("@");
  return `${name[0]}***@${domain}`;
}

console.log(maskEmail("tester@example.com"));

// console.log(email); // ReferenceError

The caller passes a value in. The function does not expose the parameter variable outside.


5. Block scope

A block is code inside {}. let and const are visible only inside that block.

javascriptjavascript
const statusCode = 404;

if (statusCode >= 400) {
  const errorMessage = "Request failed";
  console.log(errorMessage);
}

// console.log(errorMessage); // ReferenceError

Blocks appear in if, for, while, try, catch, and plain {} sections.


6. Block scope in loops

let keeps the loop variable inside the loop.

javascriptjavascript
for (let index = 0; index < 3; index += 1) {
  console.log(`Run check ${index}`);
}

// console.log(index); // ReferenceError

This prevents accidental reuse of a loop counter after the loop finishes.


7. Nested scope lookup

Inner code can read outer variables.

javascriptjavascript
const browserName = "chromium";

function describeRun() {
  const testType = "smoke";

  function buildLabel() {
    return `${browserName} ${testType}`;
  }

  return buildLabel();
}

console.log(describeRun());

Name resolution walks the scope chain from the active record outward until a binding exists or fails.


8. Outer scope cannot read inner variables

The lookup works one way: inner can read outer, but outer cannot read inner.

javascriptjavascript
function prepareUser() {
  const userEmail = "qa@example.com";
  return userEmail;
}

const preparedEmail = prepareUser();
console.log(preparedEmail);

// console.log(userEmail); // ReferenceError

Return the value when the caller needs it.


9. Shadowing

Shadowing happens when an inner variable has the same name as an outer variable.

javascriptjavascript
const status = "global status";

function printStatus() {
  const status = "local status";
  console.log(status);
}

printStatus(); // local status
console.log(status); // global status

This is legal, but often confusing. In QA code, prefer specific names:

javascriptjavascript
const defaultStatus = "passed";

function printResultStatus() {
  const actualStatus = "failed";
  console.log(actualStatus);
}

10. Scope and test helpers

Good helpers receive what they need and return what they create.

javascriptjavascript
function buildUser(email, role = "viewer") {
  return {
    email,
    role,
    isActive: true,
  };
}

const adminUser = buildUser("admin@example.com", "admin");
const viewerUser = buildUser("viewer@example.com");

console.log(adminUser);
console.log(viewerUser);

Avoid helpers that depend on hidden changing state:

javascriptjavascript
let selectedRole = "viewer";

function buildUserWithHiddenState(email) {
  return {
    email,
    role: selectedRole,
  };
}

Hidden state makes a test harder to reason about. Parameters make dependencies visible.


11. Scope in try / catch

catch creates its own block scope.

javascriptjavascript
try {
  throw new Error("Login failed");
} catch (error) {
  const message = error.message;
  console.log(message);
}

// console.log(error); // ReferenceError
// console.log(message); // ReferenceError

12. Practical scope rules for QA code

RuleWhy it helps
Prefer const by defaultPrevents accidental reassignment
Use let only when value changesMakes changing state visible
Avoid global mutable statePrevents test pollution
Return values from helpersMakes data flow explicit
Avoid confusing shadowingKeeps debugging simple

Official docs


Quick recap

ScopeVariable is visible
Global scopeFrom many places in the file or script
Function scopeOnly inside the function
Block scopeOnly inside {} block
Nested scopeInner code can read outer variables
ShadowingInner name hides an outer name

Suggested exercises

  1. Create a global constant baseUrl and use it inside a function.
  2. Create a function-local variable and prove it is not visible outside.
  3. Use let in a for loop and try to read it after the loop.

Homework

Short tasks (about 20-30 minutes). Click a task title to reveal the prompt.

Task 1: global constant

Create const baseUrl = "https://example.com" outside a function. Write function buildPath(path) that returns baseUrl + path.

Task 2: function scope

Write function createToken() with a local variable token = "abc-123". Return the token and explain why token is not available outside the function.

Task 3: block scope

Inside an if block, create const errorMessage = "Email is required". Log it inside the block. Then try to log it outside and explain the error.

Task 4: loop scope

Create a for loop with let index. Log index inside the loop, then try to log it after the loop. Explain the result.