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.
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.
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:
let currentUserEmail = "";
function rememberUser(email) {
currentUserEmail = email;
}
3. Function scope
Variables declared inside a function are available only inside that function.
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.
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.
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.
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.
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.
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.
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:
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.
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:
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.
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
| Rule | Why it helps |
|---|---|
Prefer const by default | Prevents accidental reassignment |
Use let only when value changes | Makes changing state visible |
| Avoid global mutable state | Prevents test pollution |
| Return values from helpers | Makes data flow explicit |
| Avoid confusing shadowing | Keeps debugging simple |
Official docs
Quick recap
| Scope | Variable is visible |
|---|---|
| Global scope | From many places in the file or script |
| Function scope | Only inside the function |
| Block scope | Only inside {} block |
| Nested scope | Inner code can read outer variables |
| Shadowing | Inner name hides an outer name |
Suggested exercises
- Create a global constant
baseUrland use it inside a function. - Create a function-local variable and prove it is not visible outside.
- Use
letin aforloop 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.