Skip to content
C++ Better Explained
Go back
Variable Scope in C++: Local, Global, and Block Scope Explained

Variable Scope in C++: Local, Global, and Block Scope Explained

Scope answers two questions about every variable you declare: where can this name be used, and when does this variable stop existing. Get scope wrong and you’ll see errors like 'count' was not declared in this scope, or worse, code that compiles fine and silently uses the wrong variable.


Local Scope: The Default

A variable declared inside a function belongs to that function and nothing else:

#include <iostream>

void greet() {
    int count = 5;             // local to greet()
    std::cout << count << "\n";
}

int main() {
    greet();
    // std::cout << count;     // error: 'count' was not declared in this scope
    return 0;
}

count is created when greet() starts and destroyed when it returns. main() has no idea it ever existed.

This is why two functions can both use a variable named i without interfering. Each one gets its own.


Block Scope: Tighter Than You Might Think

Scope isn’t per-function — it’s per block, meaning any pair of curly braces:

#include <iostream>

int main() {
    if (true) {
        int inside = 10;
        std::cout << inside << "\n";   // fine
    }
    // std::cout << inside;            // error: gone at the closing brace

    for (int i = 0; i < 3; ++i) {
        std::cout << i << " ";
    }
    // std::cout << i;                 // error: i belonged to the loop

    return 0;
}

The loop counter i is declared in the for header, so it lives and dies with the loop. If you need the value after the loop ends, declare it before:

int i = 0;
for (; i < 10; ++i) {
    if (someCondition(i)) break;
}
std::cout << "Stopped at " << i << "\n";

The general guidance is to declare variables in the smallest scope that works. A variable that can’t be seen can’t be accidentally modified, and its lifetime is obvious at a glance.

If you're looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you're a complete beginner or looking to solidify your understanding. Just $19.

Global Scope: Visible Everywhere

A variable declared outside every function is global:

#include <iostream>

int score = 0;                 // global

void addPoints(int n) {
    score += n;                // no parameter needed — it just reaches out
}

void resetGame() {
    score = 0;
}

int main() {
    addPoints(10);
    addPoints(5);
    std::cout << "Score: " << score << "\n";   // Score: 15

    resetGame();
    std::cout << "Score: " << score << "\n";   // Score: 0
    return 0;
}

Globals are initialised before main() runs and live until the program exits. Unlike local variables, a global with no initialiser is zero-initialised — an uninitialised local int contains garbage, an uninitialised global int is 0.


Why Globals Cause Pain

The convenience is real, and so is the cost. Suppose score ends up wrong. Which function did it? With a global, the answer is any of them — you have to read every function in the file to find out.

Compare with the alternative:

int addPoints(int score, int n) {
    return score + n;
}

int main() {
    int score = 0;
    score = addPoints(score, 10);
    score = addPoints(score, 5);
    return 0;
}

Now addPoints can’t touch anything you didn’t hand it. You can test it in isolation, and reading its signature tells you everything it can affect. That property — a function’s effects being visible from its signature — is what makes larger programs manageable.

The one case where globals are uncontroversial is constants:

constexpr double PI = 3.14159265358979;
constexpr int MAX_PLAYERS = 4;

Nothing can modify them, so none of the reasoning problems apply. See const vs constexpr for which to pick.


Shadowing: The Silent Bug

If an inner scope declares a name that already exists outside, the inner one wins:

#include <iostream>

int value = 100;               // global

int main() {
    int value = 50;            // shadows the global
    std::cout << value << "\n";       // 50

    {
        int value = 25;        // shadows the local
        std::cout << value << "\n";   // 25
    }

    std::cout << value << "\n";       // 50 again
    std::cout << ::value << "\n";     // 100 — the global, via ::
    return 0;
}

The :: prefix is the scope resolution operator with nothing on its left, which means “the global one.”

Shadowing compiles without complaint, which is exactly why it bites. The classic version is a constructor parameter shadowing a member variable:

class Player {
public:
    Player(int score) {
        score = score;   // assigns the parameter to itself — member unchanged
    }
private:
    int score;
};

Turn on -Wshadow in your compiler flags and the compiler will point these out before they cost you an afternoon:

g++ -Wall -Wshadow -std=c++17 main.cpp -o main

The fix here is a member initialiser list — Player(int score) : score(score) {} — which is unambiguous. See constructors and destructors for the full picture.


Static Locals: A Middle Ground

A local variable marked static keeps its value between calls but stays invisible outside its function:

#include <iostream>

void counter() {
    static int calls = 0;      // initialised once, on the first call
    ++calls;
    std::cout << "Called " << calls << " times\n";
}

int main() {
    counter();   // Called 1 times
    counter();   // Called 2 times
    counter();   // Called 3 times
    return 0;
}

You get a global’s lifetime with a local’s visibility — often exactly what someone reaches for a global to achieve. The static keyword in C++ covers its other uses.


Quick Reference

ScopeDeclaredLifetimeVisible to
BlockInside { }Until the closing braceThat block
LocalInside a functionUntil the function returnsThat function
Static localstatic inside a functionWhole programThat function
GlobalOutside all functionsWhole programEvery function in the file


Take Your C++ Further

If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.

👉 Get the C++ Better Explained Ebook — $19

📋

Free Download: The 10 Mistakes Every C++ Beginner Makes

A free 1-page checklist that shows the exact traps that slow down every C++ beginner — so you can avoid them from day one.

🔒 No spam. Unsubscribe anytime.


Share this post on:

Written by

Sahil Bora

Software Engineer. Author and creator of C++ Better Explained.


Previous Post
How to Make a C++ Program Wait: sleep_for and Delays Explained
Next Post
Virtual Destructor in C++: Why You Need One and When

Keep Learning