Variable Scopes in C++
The scope is determined by where the declaration sits and which block contains it.
Try it: Scope: Local, Enclosing, Global, Built-in
How Python looks up a name Local → Enclosing → Global → Built-in, why assigning to a name inside a function makes it local (and raises UnboundLocalError if it is read first), and what global, nonlocal and closures change.
How it works
- Before a function runs, Python marks every name assigned inside it as local to that function, unless it is declared global or nonlocal.
- Reading a name searches the local namespace, then enclosing functions, then the module's globals, then the built-ins; the first hit wins.
- A local name read before it has a value raises UnboundLocalError (Python does not fall back to the global).
- global x makes assignments go to the module namespace; nonlocal x makes them go to the nearest enclosing function.
- A function's local frame disappears when it returns, unless an inner function (a closure) still uses its variables.
Default run (10 steps): Ready: Local shadows global. Every name is looked up Local, then Enclosing, then Global, then Built-in. … Finished. Printed: inside: 99 / outside: 10.
Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. Built-ins shows only the names the script used.
Loading the simulation…
The Question Every Name Raises
Every program uses names to refer to values. A name is declared once, and then the rest of the code uses it. That raises a practical question: once a name has been declared, which parts of the program are allowed to use it? The answer is not 'everywhere.' The region of code where a name can be used is controlled by where the name was declared. That region is called the name's scope. This article focuses on one important kind of scope: the scope of local names.
As you read, keep one question in mind for every name you meet: where was it declared, and how far does its usefulness reach from that point? Answering that question is the skill this article builds.
What Makes a Name Local
A name declared in a function or lambda is a local name.
A function is a named unit of code, and a lambda is an anonymous function. Any name created inside either one belongs to that unit, which is why it is described as local. The word 'local' signals that the name is tied to the place where it was created rather than to the whole program.
Blocks and Their Braces
The scope rule depends on the idea of a block. A block is delimited by a { } pair. The opening brace marks where the block begins, and the matching closing brace marks where it ends. Everything between the two braces belongs to that block. The braces that delimit a function body are one example of a block; braces placed inside a function body create further blocks nested within it.
When you read code, pair each opening brace with its matching closing brace first. Once the pairs are clear, the boundaries of every block are visible, and the boundaries of every local name's scope follow from them.
Where a Local Name's Scope Begins and Ends
The source gives the scope rule in one sentence: the scope of a local name extends from its point of declaration to the end of the block in which its declaration occurs. Two boundaries are therefore involved. The start boundary is the point of declaration, meaning the position in the code where the name is introduced. The end boundary is the closing brace of the enclosing block.
Read the timeline as a walk down the page. Before the bonus declaration, bonus does not yet exist in scope. From the declaration until the inner block's closing brace, both names are usable. After that brace, only total is still in scope.
Nested Blocks and Containment
Blocks can contain other blocks, and scopes nest the same way. A name declared in an outer block is usable in the inner block as well, because the inner block lies inside the region where that outer name's scope extends. A name declared in an inner block, however, is invisible from the outer block, because its scope ends at the inner block's closing brace.
The diagram shows the containment that the scope rule depends on. The argument name and the body-level name both belong to the function as a whole. The name declared inside the nested block belongs only to that block, so its scope ends at the block's closing brace while the function-level names remain in scope for the rest of the function body.
Function Arguments and Body Declarations
Names that a function receives through its argument list are also local names. The argument names are not introduced by a separate declaration inside the body, but they still count as local names of the function, so the same scope reasoning applies to them. They belong to the function, and their use is tied to that function's scope.
This means you can treat a function's arguments and its internal declarations as one group when you ask which code can use them. Both are local names of the function. The difference lies only in where each one was introduced.
- limit: function argument, a local name whose scope covers the whole function body
- doubled: declared in the body, so its scope starts at this line
- The closing brace ends the scope of both names
Local Names Inside Lambdas
A lambda is an anonymous function, so the same definition applies to it. A name created inside a lambda, whether it appears in the parameter list or in the lambda body, is a local name of that lambda. Its scope is governed by the same two boundaries: it starts at its declaration and ends at the closing brace of the block that contains it.
- x: parameter of the lambda, a local name of the lambda
- result: declared in the lambda body, so its scope starts at its declaration
- Neither x nor result is in scope after the lambda's closing brace
Predicting Accessibility Before You Run the Code
Work through the following example by applying the two boundaries. First find where each name's scope starts, then find where it ends, and only then decide which lines may use it.
15
10- total: declared in the function body, scope runs to the function's closing brace
- bonus: declared in the inner block, scope runs only to the inner block's closing brace
- Line X lies after the inner block closes, so bonus is not accessible there
Tracing Scope Through the Example
For each use of a name in the example above, decide whether the name is accessible at that line.
- 1Locate the declaration of total
total is declared on the second line of main, at the function-body block level. Its scope starts there and runs to the closing brace of main.
- 2Check the use of total inside the inner block
The inner block lies inside main's body, which is inside total's scope. total is accessible, so total + bonus is valid.
- 3Locate the declaration of bonus
bonus is declared inside the inner block. Its scope starts at its declaration and ends at the inner block's closing brace.
- 4Check the use of bonus at line X
Line X comes after the inner block's closing brace, outside bonus's scope. bonus is not accessible there, so this line would not compile.
total is accessible at every line after its declaration. bonus is accessible only between its declaration and the inner block's closing brace.
What do you think happens?
In this code, does line A compile, and if not, why not?
Reveal answer
No, because the scope of count starts at its declaration, which comes after line A
The scope of a local name starts at its point of declaration, not at the top of the block. Line A appears before the declaration on line B, so count is outside its scope at line A and the program does not compile.
- Line A appears before the declaration of count on line B
- count's scope begins at line B, so line A cannot use it
Mistakes Beginners Make with Local Scope
Common mistake Assuming a name is usable from the top of its block
What it looks likeUsing count on a line above int count = 3; inside the same block
Why it failsThe scope starts at the point of declaration, not at the top of the block.
Do this insteadOnly use a name on lines that come after its declaration within its block.
Common mistake Using a name declared in an inner block after that block has closed
What it looks likePrinting bonus on a line after the inner braces close
Why it failsThe scope of bonus ends at the closing brace of the block that contains its declaration.
Do this insteadMove the use inside the block, or declare the name in an enclosing block if it must be used outside.
Common mistake Treating function arguments as if they were not local names
What it looks likeAssuming limit is somehow global because it was not declared with a statement in the body
Why it failsArgument names are local names of the function and follow the same scope reasoning.
Do this insteadApply the same boundary check to arguments as to body declarations.
Common mistake Mismatching braces, so that you think a block ends at the wrong line
Practice Predicting Accessibility
Take the following function. For the lines marked L1 and L2, state whether the name x is accessible, and give the boundary that decides your answer.
void demo(int x) {
{
int y = x + 1; // L1
}
int z = y; // L2
}Hints
- Start by identifying which block declares y and where that block's closing brace is.
- x is an argument, so its scope covers the whole body of demo.
Key Takeaways
- A name declared in a function or lambda is a local name, and function argument names are local names too.
- The scope of a local name runs from its point of declaration to the closing brace of the block that contains that declaration.
- A block is delimited by a { } pair, so matching braces determine exactly where a local name's scope ends.
- A name is not accessible on lines that come before its declaration, even inside the same block, and not on lines after its enclosing block closes.