The scope of a variable settles two things: where it is visible in the program, and how long it lives. A variable declared inside a function is local: it is born when the function is called and dies when it returns. Declared outside any function, it is global: it lives for as long as the program runs. In embedded work this distinction is very concrete: it is what decides whether a value survives from one pass of loop() to the next or resets on every pass.
What is it for?
This is one of the most frequent misreadings a beginner makes: declaring a variable inside loop(), believing it “remembers” something, and finding that it starts again from zero on every pass. Understanding scope means knowing where to declare a variable according to its use:
a working value, useful for the length of a calculation → local (the default, and the safest);
a state to be kept between passes (a counter, a last instant, a current mode) → global (or static).
Placing your variables well avoids both the “memory that wipes itself” bugs and the conflicts of a global variable modified from everywhere.
The local variable
A variable declared inside a function exists only in that function, and only for the duration of its execution:
void loop() { int compteur = 0; // LOCAL: recreated on every pass of loop() compteur = compteur + 1; Serial.println(compteur); // always prints 1! delay(500);}
On every pass, compteur is recreated and reset to 0, incremented to 1, printed, then destroyed at the end of the pass. Hence the display stuck at 1. That is fine for a throwaway value, but not for remembering anything at all.
The global variable
Declared outside any function (at the top of the sketch, among the global declarations seen in the structure of a program), a variable is visible everywhere and lives for the whole program:
int compteur = 0; // GLOBAL: created once, kept from then onvoid loop() { compteur = compteur + 1; Serial.println(compteur); // 1, 2, 3, 4, ... delay(500);}
Here compteur is created only once, at start-up. It keeps its value from one pass to the next: the display climbs. That is what you need for any state that has to persist.
Tip
The mental test. “Does this value have to survive into the next pass of loop()?” If yes → global. If no → local. Most variables are local. You only make something global when it really has to be shared or remembered.
The middle ground: static
Sometimes you want a variable to persist between calls to a function without exposing it to the whole program. The static keyword does exactly that: the variable stays local (invisible elsewhere), but it is initialised only once and keeps its value afterwards:
void loop() { static int compteur = 0; // initialised once, kept between passes compteur = compteur + 1; Serial.println(compteur); // 1, 2, 3, ... like a global, but confined to loop() delay(500);}
It is a good reflex when a single function needs a memory of its own: it saves you from cluttering the global space.
Block scope
A variable declared inside a { } block (the body of an if, of a for) exists only in that block:
for (int i = 0; i < 10; i++) { // i exists only here}// i no longer exists on this line
That is deliberate: i is a tool of the loop, of no use outside it. Trying to use it afterwards gives an 'i' was not declared in this scope error.
Code to read
This sketch counts presses on a button. The running total has to survive between passes (so it is global), while the reading of the button is a working value (so it is local).
int nbAppuis = 0; // GLOBAL: the running total, kept between passesconst int BOUTON = 2;void setup() { pinMode(BOUTON, INPUT_PULLUP); // pull-up: button released = HIGH Serial.begin(115200);}void loop() { int etat = digitalRead(BOUTON); // LOCAL: recomputed every pass, throwaway if (etat == LOW) { // pressed (pull-up: pressed = LOW) nbAppuis = nbAppuis + 1; Serial.print("Presses so far: "); Serial.println(nbAppuis); delay(200); // crude debounce (see arduino-entree-tor) }}
nbAppuis has to be global, otherwise the counter would start from zero on every pass and would never print anything but 1. etat has no reason to survive: local, it stays isolated and readable.
Pitfalls
The local variable you thought was persistent. Pitfall number one: a counter or a last state declared inside loop() resets on every pass. To remember it, declare it global or static.
Shadowing. Declaring a local variable with the same name as a global “hides” the global inside that function: you think you are modifying the global, you are only touching a local copy. Avoid reusing the same names.
An uninitialised global. An uninitialised global variable is worth 0 by default. An uninitialised local holds an undefined value (anything at all). Always initialise your locals.
Too many global variables. When everything is global, any function can modify anything: bugs become hard to pin down. Keep the global space to the strict minimum (shared state, persistence).
Exercises
Exercise 1 — Predict the output
For each of these two loop() functions, what does the serial monitor print on the first three passes?
// Version Avoid loop() { int n = 0; n++; Serial.println(n); delay(500); }// Version Bint n = 0;void loop() { n++; Serial.println(n); delay(500); }
Answer to exercise 1
Version A: 1, 1, 1. n is local, recreated and reset to 0 on every pass, so always incremented from 0 to 1.
Version B: 1, 2, 3. n is global, created only once. It keeps its value and is incremented pass after pass.
Exercise 2 — The counter that does not count
This program is meant to count how many times a reading goes past a threshold, but it always prints 1. Fix it in two different ways.
void loop() { int depassements = 0; if (analogRead(A0) > 500) { depassements++; Serial.println(depassements); } delay(100);}
Answer to exercise 2
depassements is local → reset to 0 on every pass. Two possible fixes:
// Fix 1: a global variableint depassements = 0;void loop() { if (analogRead(A0) > 500) { depassements++; Serial.println(depassements); } delay(100);}
// Fix 2: a static local variable (confined to loop, but persistent)void loop() { static int depassements = 0; if (analogRead(A0) > 500) { depassements++; Serial.println(depassements); } delay(100);}
Where it fits in the project
Step 4 of the proof of concept phase — as soon as a program has to remember a state (last mode, counter, last instant), scope becomes decisive. A poor choice produces “memory that wipes itself” bugs that are hard to understand.
State machines — the current state of a state machine is typically a persistent global variable: scope is the bedrock of the mechanism.