Concepts / Variable Scopes in C++

Variable Scopes in C++

The scope is determined by where the declaration sits and which block contains it.

  • Programming
Interactive lab

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

  1. Before a function runs, Python marks every name assigned inside it as local to that function, unless it is declared global or nonlocal.
  2. Reading a name searches the local namespace, then enclosing functions, then the module's globals, then the built-ins; the first hit wins.
  3. A local name read before it has a value raises UnboundLocalError (Python does not fall back to the global).
  4. global x makes assignments go to the module namespace; nonlocal x makes them go to the nearest enclosing function.
  5. 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.

Educational simulation

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

Definition

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.

cpp
Notes on the code
  • 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.

cpp
Notes on the code
  • 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.

cpp
Output
15
10
Notes on the code
  • 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
Worked example

Tracing Scope Through the Example

For each use of a name in the example above, decide whether the name is accessible at that line.

  1. 1
    Locate 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.

  2. 2
    Check 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.

  3. 3
    Locate 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.

  4. 4
    Check 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.

Result

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?

  • Yes, it prints 3 because count exists throughout main

  • No, because the scope of count starts at its declaration, which comes after line A

  • No, because a name can only be used inside a block that is not the function body

Reveal answer
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.

cpp
Notes on the code
  • 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 like

    Using count on a line above int count = 3; inside the same block

    Why it fails

    The scope starts at the point of declaration, not at the top of the block.

    Do this instead

    Only 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 like

    Printing bonus on a line after the inner braces close

    Why it fails

    The scope of bonus ends at the closing brace of the block that contains its declaration.

    Do this instead

    Move 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 like

    Assuming limit is somehow global because it was not declared with a statement in the body

    Why it fails

    Argument names are local names of the function and follow the same scope reasoning.

    Do this instead

    Apply 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

MEDIUM

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.

Related concepts

More concepts starting with V