C Programming Series ยท Variables, Storage, and Visibility
๐ฏ Why This Matters
Every variable you’ve declared throughout this course has had an invisible property attached to it the whole time: scope โ the region of the program where that variable’s name is actually recognized. This post covers scope using only what you already know: variables declared outside main() (global), variables declared directly inside main() (local), and variables declared inside a control statement’s block โ an if, a for, or a while โ nested within main(). We’ll also work through the question that trips up almost everyone the first time: what happens if the exact same variable name is declared in more than one of these places at once?
๐ง The Core Idea: Nested Boxes

Think of every pair of curly braces { } as drawing a box. A variable declared inside a box is visible from that point onward, until the box closes โ and it’s also visible inside any smaller box nested within it. But it’s invisible to anything outside that box. main() is itself one big box, and every if, for, or while block inside it draws a smaller box nested within that one.
1๏ธโฃ Global Variables
A variable declared outside main() โ usually near the top of the file โ is global. It lives in the outermost box, so main() (and every block inside it) can see and use it freely.
#include <stdio.h>
int total = 100; // global variable
int main() {
printf("total = %d\n", total);
total = total + 50;
printf("total after update = %d\n", total);
return 0;
}
๐ฅ๏ธ Output:
total = 100
total after update = 150
total is declared outside main(), so it’s already visible the moment main() starts โ no separate declaration needed inside. Updating it inside main() permanently changes the one and only copy of total that exists.
2๏ธโฃ Local Variables Inside main()
A variable declared directly inside main()‘s own body โ not inside any further nested block โ is local to main(). It exists for the entire run of the program (since main() itself doesn’t end until the program does), but it’s still invisible to anything declared outside main().
#include <stdio.h>
int main() {
int x = 10;
int y = 20;
printf("x = %d, y = %d\n", x, y);
printf("Sum = %d\n", x + y);
return 0;
}
๐ฅ๏ธ Output:
x = 10, y = 20
Sum = 30
x and y only exist inside main()‘s box. If this program had anything declared outside main(), that outer code would have no way to see x or y โ the visibility only flows outward-in, from global down into main(), never the other way.
3๏ธโฃ Local Variables Inside Control Statements
A variable declared inside an if, for, or while block is scoped to that block only โ a smaller box nested inside main()‘s box.
Inside an if Block
#include <stdio.h>
int main() {
int marks = 78;
if (marks >= 50) {
int bonus = 5;
printf("Passed! Bonus points: %d\n", bonus);
printf("Final score: %d\n", marks + bonus);
}
// printf("%d", bonus); // ERROR โ bonus doesn't exist here
return 0;
}
๐ฅ๏ธ Output:
Passed! Bonus points: 5
Final score: 83
bonus is declared inside the if block, so it only exists between that block’s opening and closing braces. marks, declared directly in main(), is visible both inside and outside the if block โ but bonus only works in the other direction: visible inside, gone outside.
Inside a for Loop
#include <stdio.h>
int main() {
int sum = 0;
for (int i = 1; i <= 5; i++) {
int square = i * i;
sum += square;
}
printf("Sum of squares: %d\n", sum);
// printf("%d", square); // ERROR โ square doesn't exist here
// printf("%d", i); // ERROR โ i doesn't exist here either
return 0;
}
๐ฅ๏ธ Output:
Sum of squares: 55
Both i (the loop counter) and square (declared inside the loop body) are scoped to the for loop’s box. sum, on the other hand, was declared directly in main() before the loop โ so it survives after the loop ends, which is exactly why the final printf can still read its accumulated value.
Inside a while Loop
#include <stdio.h>
int main() {
int n = 1234, digitCount = 0;
while (n != 0) {
int lastDigit = n % 10;
digitCount++;
n /= 10;
}
printf("Number of digits: %d\n", digitCount);
return 0;
}
๐ฅ๏ธ Output:
Number of digits: 4
lastDigit is recreated fresh on every single pass of the loop and discarded at the end of each pass โ it’s not even carried over from one iteration to the next, since the block it lives in closes and reopens every time.
๐ญ The Big Question: Same Name, Declared Twice
What happens if main() declares a variable, and then a control block inside main() declares another variable with the exact same name? This is called shadowing โ the inner one temporarily hides the outer one.
Shadowing Inside an if Block
#include <stdio.h>
int main() {
int x = 100;
printf("Before if: x = %d\n", x);
if (x > 0) {
int x = 5; // a brand-new, separate x โ shadows main's x
printf("Inside if: x = %d\n", x);
}
printf("After if: x = %d\n", x);
return 0;
}
๐ฅ๏ธ Output:
Before if: x = 100
Inside if: x = 5
After if: x = 100
This is the key thing to understand: the int x = 5; inside the if block creates a completely separate variable โ it does not modify main()‘s x at all. For the duration of the if block, any use of x refers to the inner one. The moment the block’s closing brace is reached, the inner x disappears, and main()‘s original x โ untouched the entire time, still 100 โ becomes visible again.
Shadowing Inside a for Loop
#include <stdio.h>
int main() {
int count = 1;
printf("Before loop: count = %d\n", count);
for (int i = 0; i < 3; i++) {
int count = i * 10; // shadows main's count
printf("Inside loop: count = %d\n", count);
}
printf("After loop: count = %d\n", count);
return 0;
}
๐ฅ๏ธ Output:
Before loop: count = 1
Inside loop: count = 0
Inside loop: count = 10
Inside loop: count = 20
After loop: count = 1
Notice main()‘s count stays exactly 1 the entire time โ the loop’s inner count is recreated fresh on every pass (starting over each time, from i * 10), completely independent of the outer one. If the inner declaration had been left out โ just count = i * 10; without int โ it would modify main()‘s variable directly, since it would no longer be a new declaration at all, just a plain assignment to the existing one.
Shadowing a Global Variable Too โ Three Levels at Once

#include <stdio.h>
int x = 1; // global x
int main() {
int x = 2; // shadows global x
printf("In main: x = %d\n", x);
if (x > 0) {
int x = 3; // shadows main's x too
printf("In if-block: x = %d\n", x);
}
printf("Back in main: x = %d\n", x);
return 0;
}
๐ฅ๏ธ Output:
In main: x = 2
In if-block: x = 3
Back in main: x = 2
Three completely separate variables, all named x, nested three boxes deep. Each one only hides the box directly outside it โ the if block’s x hides main()‘s x, which itself hides the global x. None of them ever touch each other’s values. The global x, in fact, is never even printed in this program โ it’s been shadowed from the very first line of main() onward.
๐ก The rule that explains all of this:When C sees a variable name, it always uses the version declared in the innermost box that contains that point in the code. Outer versions of the same name still exist and keep their values โ they’re just temporarily unreachable by that name, for as long as the inner one is in scope.
๐ซ Common Mistakes Beginners Make
| Mistake | Why it happens |
|---|---|
| Using a block’s variable outside that block | The variable’s box has already closed โ this is a compile error, not a silent bug |
| Expecting a shadowed inner variable to change the outer one | A new declaration (with a type, like int x = 5;) always creates a separate variable, even if the name matches one from an outer scope |
| Forgetting the type keyword and accidentally modifying the outer variable | Writing x = 5; instead of int x = 5; inside a block assigns to the existing outer x instead of creating a new one โ the opposite of shadowing |
| Assuming a for-loop’s inner variables survive between passes | Anything declared inside the loop body is recreated fresh on every single pass, not carried over |
๐ Practice Exercises
Exercise 1: Predict the Output
int n = 50;
int main() {
int n = 5;
for (int i = 0; i < 2; i++) {
int n = i + 1;
printf("%d ", n);
}
printf("%d\n", n);
return 0;
}
(Answer: 1 2 5 โ the loop’s n is recreated each pass from i + 1; once the loop ends, main()‘s own n (still 5) is what prints.)
Exercise 2: Shadow or Modify?
int main() {
int total = 10;
if (total > 0) {
total = total + 5; // no 'int' here!
printf("%d\n", total);
}
printf("%d\n", total);
return 0;
}
(Answer: 15 then 15 โ since there’s no int before total inside the if-block, this is not a new declaration. It directly modifies main()‘s own total, so the change is still visible after the block ends.)
Exercise 3: Build It Yourself
Write a program with a global variable rate = 10, a local variable of the same name inside main() set to 20, and a while loop inside main() that declares yet another rate set to 30. Print rate from all three locations and confirm each one holds its own separate value.
โ Frequently Asked Questions
Q: Is it bad practice to shadow variables like this on purpose?
Generally yes โ while it’s completely legal, reusing a name across nested scopes makes code harder to read correctly at a glance, and it’s an easy source of bugs if you later forget which x a particular line is actually referring to. Distinct names are almost always clearer.
Q: How do I tell the difference between shadowing and just modifying the outer variable?
Look for a type keyword. int x = 5; inside a block is a new declaration โ it shadows. Plain x = 5; with no type is an assignment to whichever x is already visible at that point โ it modifies, it doesn’t shadow.
Q: Does it matter which control statement is used โ does if behave differently from for or while for this?
No โ if, for, and while all create a nested block the same way, and variables declared inside any of them follow identical scope rules. The loop constructs just additionally scope their own loop-control variables (like a for loop’s counter) to that same block.
โ Key Takeaways
- A global variable (declared outside
main()) is visible everywhere, including insidemain()and every block nested within it. - A local variable declared directly in
main()is visible for all ofmain(), including inside anyif,for, orwhileblock โ but not beforemain()even starts. - A variable declared inside a control statement’s block only exists within that block โ it disappears the moment the block’s closing brace is reached.
- Declaring a variable with the same name in an inner block creates a completely separate variable that shadows the outer one for as long as the inner block runs โ the outer variable’s value is never touched.
- The difference between shadowing and modifying comes down to one thing: whether a type keyword is present. With a type, it’s a new, separate variable. Without one, it’s a direct change to whichever variable was already visible.
๐ Next up: With scope covered using just main() and control blocks, we’ll look at functions โ how splitting a program into smaller, named pieces changes the scope picture even further, and introduces a whole new set of rules for passing values between them.