LEARN TO CODE | JAVASCRIPT | 02 FUNCTIONS AND COLLECTIONS
02.4 Scope and debugging
Trace where names are available, inspect changing values and use evidence to find and repair a logic error.
01 | A NAME HAS A REACH
You created a variable inside a function, but logging it outside gives an error. Another variable appears to have a different value depending on where you pause. These problems are easier to explain once you understand scope.
Scope is the region in which a name can be accessed. It is about the name’s availability, not simply whether its value has been calculated.
Debugging is the process of finding the cause of incorrect behaviour, correcting it and checking the correction. We will use scope examples, then investigate a program that runs without an error message but gives the wrong total.
Keep index.html and script.js in the same VS Code folder. Put the current example into script.js, save it, open or refresh index.html in your browser and look in that page’s Developer Tools Console.
Replace the previous example unless a task explicitly says to add to it. Pasting several independent examples together can redeclare the same let or const name. The downloadable examples use a separate folder for each complete program.
02 | OUTER AND FUNCTION-LOCAL NAMES
const pointsPerWin = 5;
function scoreFor(wins) {
const score = wins * pointsPerWin;
return score;
}
const finalScore = scoreFor(3);
console.log(finalScore);
pointsPerWin is declared at the top level of this classic script. The function can read it from an outer scope. wins and score belong to the function invocation and cannot be accessed as those local names outside it.
Returning the score’s value is how the caller receives the result. The caller stores it under a different name, finalScore. Returning does not make the local name score global.
Top-level names in these classic scripts are broadly available to the page’s scripts. Top-level let and const bindings are not automatically properties such as window.pointsPerWin. Modules have their own top-level scope; this course will introduce modules later.
03 | BRACES CAN CREATE A BLOCK SCOPE
const isMember = true;
let message = "Standard entry";
if (isMember) {
const discount = 2;
message = `Member discount: ${discount}`;
}
console.log(message);
discount is a block-local const, available within the if block. message was declared outside and assigned a new value inside, so it is still available after the block.
Trying to read discount after the closing brace would raise a ReferenceError. Moving every variable to the top level is not the best fix: keep local details local and pass or return the information that is actually needed elsewhere.
let and const obey block scope. The older var follows different rules, which is one reason these lessons consistently use let and const.
04 | SAME SPELLING, DIFFERENT BINDING
const status = "Outside";
function showStatus() {
const status = "Inside";
console.log(status);
}
showStatus();
console.log(status);
The local status shadows the outer status. Inside the function, the nearer declaration is used. The function does not modify the outer value.
Shadowing is legal, but confusing names can make it harder to see which value is in use. Prefer clear names and small functions over relying on subtle scope interactions.
For let and const, declare a name before reading it. Access before its declaration is reached can raise a ReferenceError even when an outer binding has the same name. We do not rely on that pattern in these programs.
TRY IT YOURSELF
Why does a parameter let a function work with different data without changing a global variable?
Reveal guidance
Each call receives its own parameter binding for the supplied value. The function can calculate a result and return it, keeping the dependency visible at the call.
05 | THREE KINDS OF BUG
| Kind | Typical evidence | Example |
|---|---|---|
| Syntax error | The source cannot be parsed | An unmatched closing parenthesis |
| Runtime error | Execution reaches an operation that fails | Reading a property of undefined |
| Logic error | Program runs but result disagrees with the requirement | Replacing a total instead of adding to it |
Read the first relevant diagnostic and source line. Then compare the program’s actual state with the expected state. A clean console is useful, but it does not prove that your algorithm is correct.
Change one thing at a time. If several edits happen together, you may fix the symptom without learning which change addressed the cause.
06 | INVESTIGATE THIS LOGIC ERROR
The requirement is to add every score. For [4, 7, 3], the expected result is 14. This deliberately buggy version instead reports 3:
function totalScores(scores) {
let total = 0;
for (const score of scores) {
total = score;
}
return total;
}
const scores = [4, 7, 3];
const total = totalScores(scores);
console.log(`Total: ${total}`);
The syntax is valid, and every name exists. JavaScript therefore has no reason to report an error. The defect is in what the assignment means.
Predict the value of total after each pass before opening the guidance. A useful hypothesis is “the old accumulated value is being discarded on every pass”. That is specific enough to test.
| Pass | Current score | Buggy total afterwards | Required total afterwards |
|---|---|---|---|
| 1 | 4 | 4 | 4 |
| 2 | 7 | 7 | 11 |
| 3 | 3 | 3 | 14 |
07 | LOG VALUES AT THE RIGHT PLACE
function totalScores(scores) {
let total = 0;
for (const score of scores) {
console.log(`Before: total=${total}, score=${score}`);
total = score;
console.log(`After: total=${total}`);
}
return total;
}
console.log(totalScores([4, 7, 3]));
These temporary logs show both inputs to the suspicious step and the result afterwards. At pass two, 4 is replaced by 7, confirming the hypothesis.
Use clear labels so a number has context. Remove noisy investigation logs once the issue is understood, keeping useful application output. Avoid logging real passwords, tokens or personal data during debugging.
08 | PAUSE IN THE BROWSER DEBUGGER
- Open the example page in Chrome or Edge and open Developer Tools.
- Choose Sources , then locate script.js. Firefox uses a Debugger panel for this job.
-
Click the line number for
total = score;to set a breakpoint. - Reload the page. Execution should pause before that statement runs.
- Inspect total and score in the local Scope view. Step over once to execute the assignment, then compare the values.
- Resume to reach the next pass. The second pass shows the wrong update clearly.
A breakpoint pauses before its statement executes. Step over runs the next statement without deliberately entering a called function. Step into follows a call into the function body; step out continues until the current function returns.
The Call Stack shows the chain of active calls. Selecting a frame changes which call’s local variables you inspect; it does not rerun that call.
09 | DEBUG THE SAME PAGE FROM VS CODE
If you want to inspect values beside your VS Code source, open the downloaded 04-debugging folder itself as your VS Code workspace. It contains index.html, script.js and a .vscode/launch.json file.
For a manual setup, create a .vscode folder inside your practice folder, then create launch.json there with this configuration:
{
"version": "0.2.0",
"configurations": [
{
"type": "msedge",
"request": "launch",
"name": "Debug this page in Edge",
"file": "${workspaceFolder}/index.html"
},
{
"type": "chrome",
"request": "launch",
"name": "Debug this page in Chrome",
"file": "${workspaceFolder}/index.html"
}
]
}
- Open script.js and click the editor margin beside the total update to add a breakpoint.
- Open Run and Debug , choose the Edge or Chrome configuration matching an installed browser, then click the green start button or press F5.
- The browser opens this workspace’s index.html. When paused, inspect Variables , add total to Watch and use the stepping controls.
- Stop the debug session when finished. Save edits and start or reload the debug page to test again.
This launches a browser for the HTML file; it does not run script.js as a Node program. No local HTTP server is needed for this simple classic-script example. If a school device blocks debugger launching, use the browser Developer Tools route instead.
10 | FIX THE CAUSE, THEN RETEST
function totalScores(scores) {
let total = 0;
for (const score of scores) {
total += score;
}
return total;
}
const scores = [4, 7, 3];
const total = totalScores(scores);
console.log(`Total: ${total}`);
The corrected update preserves the old total and adds score. The rest of the function can stay the same. The return is after the loop, so every element is included before the result is handed back.
| Input | Expected total | Purpose |
|---|---|---|
| [4, 7, 3] | 14 | Several elements |
| [9] | 9 | One element |
| [] | 0 | No elements; the loop body is skipped |
| [0, 5] | 5 | Zero is valid numeric data |
These tests assume arrays of valid numbers. Handling arbitrary strings or external records is an additional validation requirement. Do not mistake passing a few known-data tests for proof about every possible input.
11 | A REPEATABLE DEBUGGING ROUTINE
TRY IT YOURSELF
Explain why returning total from inside the loop would still be a bug after fixing the addition. Use [4, 7, 3] to demonstrate.
Reveal guidance
The first pass would add 4 and immediately return, skipping 7 and 3. With an empty array, no return in the body would execute. Keep the final return after the loop for this task.
State the requirement, choose a small test, predict the state, inspect where reality first differs, fix one cause and retest boundaries. Save a short record of the defect and evidence so someone else can reproduce the repair.
Next, put functions, arrays, objects and debugging together in the section challenges.
KEEP LEARNING
Previous page 02 Functions and collections: challenges
Download Section 02 example programs
Further reading: Functions · Arrays · Objects · VS Code browser debugging