A blog where I share thoughts on code and practices

In this article I'll show an example where avoiding a global variable has led to a bug, I'll define what global variables are, explain the problem, and then give examples where I have used them successfully.

Global Variables Are Not the Problem

We're all taught that global variables are bad. They can be modified from anywhere, sometimes force functions to be called in a specific order, and can be impossible to debug if the program is large enough or the state is random enough. We're usually taught not to use them within the first year of programming, but many of us never figure out when we should.

First, we'll look at code without globals. Here we want to see how many times the 'simple' function is entered before it throws an exception (not shown) so we can set a breakpoint at the top of the function. There's a bug in our counter logic. If the problem isn't obvious you may find this frustrating.

let counter = { count:0 }
let obj = { counter:counter };
function simple(obj) { 
	console.log(++obj.counter.count) 
	if (obj.counter.count == 123) {
		//let's set a breakpoint before the exception
	}
	/* rest of func with buggy logic */
}
function complex(obj) {
	let temp = structuredClone(obj)
	simple(temp)
	simple(temp)		
}
simple(obj)
simple(obj)
complex(obj)
simple(obj)

If you run the code you'll see 1 2 3 4 3 printed instead of 5. Before I tell you the problem let's look at the version that uses a global variable, which runs correctly.

let count = 0 //global
let obj = { };
function simple(obj) { 
	console.log(++count) 
	if (count == 123) {
		//let's set a breakpoint before the exception
	}
	/* rest of func with buggy logic */
}
function complex(obj) {
	let temp = structuredClone(obj)
	simple(temp)
	simple(temp)		
}
simple(obj)
simple(obj)
complex(obj)
simple(obj)

The problem is that structuredClone made a deep copy of our object. When the complex function executed the simple function, the wrong counter was modified, causing us to see repeating numbers and making us unsure of the correct time to breakpoint.

Now that we can see avoiding globals can still be problematic let's define what a global variable is and what counts as using one.

Defining Global Variables

My definition of a global variable is any variable that isn't passed in as an argument or defined within the function, Here are some types of variables across different languages.

Would you consider the below as using a global variable?

The Problem?

The problem is data access. Nothing more, nothing less. There's a term for this that has nothing to do with global variables: "action at a distance." If a program keeps copies of a pointer you passed in, you may have objects that affect another when you had no idea they had any association. One reason people like to clone objects is to avoid unexpected mutations. However, in the example code above, a clone actually causes the problem.

When you make a mistake with global variables it's pretty easy to blame the fact that it's global. People usually don't recover from mistakes by cloning the global and restoring the value. It's also easy to have bad habits when using them. Beginners might be lazy and use a global variable instead of changing the signature of a dozen functions, which blows up in their faces when they overwrite a value they need.

Global Variable Use Cases

I try not to write long posts. If you'd like code samples let me know and I may write a follow-up article. Here are some cases where I like to use globals.

With a little encapsulation, you can make globals error-proof, after all, no one ever complains about print or memory allocations except for having too much of them. As an example of encapsulation the global counter in our original example could be an inc() function. The append-only worklist could either be accessed behind a function or a type that only allows append operations.