When working with variables in programming, it is essential to understand the scope of the variable. This is one of the topics that many programmers find the hardest to understand (when, in reality, it is very simple).
The scope of a variable is the context in which a variable exists and can be used. Outside of its scope, the variable does not exist (and therefore, we cannot use it).
Just as we saw when looking at the lifecycle of a program, a variable has its own “lifecycle.” In general, during our program,
- We declare / create the variable
- We use the variable
- The variable is destroyed
The area where we can use the variable is its scope. In general (spoiler!) in almost all modern languages, the variable exists from the moment it is created until the end of the block in which it is defined.
It is intuitive to think that we cannot use a variable before creating it (although we will see that some languages are special, right JavaScript?).
The other factor that is missing: when does the variable stop existing? Well, unless we manually free it, the variable is automatically destroyed when exiting the block where it was created.
So the scope of existence of the variable includes all instructions and nested blocks found between the creation and destruction of the variable, which usually occurs at the end of the block in which it was created.
What, put in code form, looks something like the following:
// here NOT available ❌
{
// here NOT available ❌
int myVariable = 5;
// here IS available ✔️
{
// in this inner block IS also available ✔️
}
}
// here NOT available ❌
Examples of variable scope in different languages
Let’s see the scope of the variable in different languages
In C#, the scope of a variable can be a code block, a function, or the entire class:
class ExampleClass
{
int classVariable = 5;
void ExampleFunction()
{
int functionVariable = 10;
if(condition)
{
int blockVariable = 15;
}
}
}
Here
classVariable
would be available throughout theExampleClass
functionVariable
would be available within theExampleFunction
blockVariable
would be available within the conditional
This behavior is more or less the standard in every programming language.
For example, in Python
def example():
functionVariable = 10
if True:
blockVariable = 15
print(blockVariable)
print(functionVariable)
JavaScript deserves its own “honorary mention” when talking about variable scope. In its early versions, it had the “brilliant idea” of being different from the others (spoiler, it didn’t go well 😅)
I will leave a detailed explanation for when I create a course on JavaScript, but in summary, initially, variables in JavaScript had function scope instead of block scope (pause for applause 👏👏).
if (true) {
var x = 20; // this has function scope, not if scope 😱
}
This led to many strange and unexpected situations. It caused so many problems that in later versions they had to try to fix it by introducing let and const, which work with block scope.
if (true) {
let x = 20; // this has if scope
}
In summary, there are few languages where the variable scope is not block-based (as far as I can think, BASIC and JavaScript). But since JavaScript is so widely used and important, it seemed important to highlight it.
Global variables and local variables
In many programming languages, it is possible to define global variables, which can be accessed from anywhere in the program.
For example, let’s suppose an Arduino program (which is based on C++)
int globalVariable = 10; // Declaration of a global variable
void setup() {
}
void loop() {
// Show the value of the global variable on the serial monitor
Serial.println(globalVariable);
}
For example, here we would have an example of global variables in Python
x = 10
def myfunc():
# Show the value of the global variable in the console
print(x)
Although Python also has its “special” quirks. If we wanted to modify the value of x
, instead of just querying it, we would need to use the reserved word global
. But that will be for when I create a course on Python.
In JavaScript
let globalVariable = 10;
function updateGlobalVariable() {
// Show the value of the global variable in the console
console.log(globalVariable)
}
In general, almost all languages have global functions implemented, in one way or another, or with their language peculiarities. But they have them.
Best practices Tips
It is often said that using global variables is a bad practice. Well, it is not always a bad practice. In fact, it’s not just that they have their utility; they are necessary for the program to work (the entry point, for instance, is a global variable).
If you stop to think, there is actually no conceptual difference between a global variable and a local variable. A global variable also has its scope. It’s just that since it is outside of any block, its “block” is the entire program.
What is true is that abusing global variables is a bad practice. In general, we should always use local variables whenever possible, and use global variables in the few cases where it is truly essential.
Let’s see a very simple example in a hypothetical machine that reads a temperature and uses a function to modify it.
int rawTemperature; // Declaration of a global variable
void correct_temperature() {
rawTemperature *= 2; // Do anything with your global variable
}
void loop() {
rawTemperature = 10;
correct_temperature();
}
Raising all variables as global is quite messy… well, you get what I mean. It is much better if instead we use local variables, and each function has defined the parameters it needs and returns.
int correct_temperature(int temperature) {
return temperature * 2;
}
void loop() {
int rawTemperature = 10;
int corrected_temperature = correct_temperature(rawTemperature);
}
In the long run, the code is much more maintainable, better isolated, reusable, etc., etc.
Low-level explanation Advanced
Let’s try to explain briefly how the scope of a variable works and the reasons for its existence at a low level.
In compiled languages like C++, when we create a variable within a block, it is stored on the stack. Regardless of whether the “content” of the variable goes to the heap, the variable itself always goes to the stack.
Then the block performs its actions and its tasks. When the block ends, the stack must be left clean and in a neutral state. During this process, local variables are removed.
That is to say, at a low level, the fact that variables are always associated with the block in which they are created *is an imposition due to the way the processor works.
In semi-compiled languages like Java or C# or interpreted languages like Python, it does not have to be like that. Here the memory manager or interpreter could decide to keep the variable after the block ends.
In fact, we have seen that JavaScript, initially, did not follow this norm… and look how it went for them.
However, the same rules have been followed because they are intuitive and practical. The fact that a variable has local character and ceases to exist when it leaves its scope seems to make sense, doesn’t it?
This promotes code reuse and reduces the possibility of errors. Therefore, the more ‘modern’ programming languages continue to maintain the same rules, although, unlike lower-level languages, they would not strictly be obliged to comply with them.