Home / Articles / Global Variables
Global Variables
The Problem with Global Variables
Global variables are variables that are accessible from any part of a program. While they may seem convenient, they introduce a number of serious problems in software design.
Tight coupling — Any function or module that reads or writes a global variable is implicitly coupled to every other function or module that does the same. This makes it hard to reason about any one piece of code in isolation.
Hidden dependencies — When a function's behavior depends on a global variable, that dependency is invisible in the function's signature. Callers cannot know from the interface alone what state the function depends upon.
Concurrency hazards — In multi-threaded programs, global variables shared between threads become race conditions waiting to happen. Proper synchronization adds complexity and overhead.
Testing difficulty — Unit tests need to set up and tear down global state between runs, and if tests run in parallel, global state causes non-deterministic failures.
Namespace pollution — In large codebases, global names from different modules can collide, causing subtle bugs.
Environment Variables
Environment variables are a special case of global state that is set by the operating system or shell before a process starts. They are used to pass configuration into programs without embedding it in the source code.
Common examples:
PATH— The list of directories where the shell searches for executablesHOME— The current user's home directoryLD_LIBRARY_PATH— Directories to search for shared libraries
In C, environment variables are accessible via:
#include <stdlib.h>
char *value = getenv("MY_VARIABLE");
Or as a third argument to main:
int main(int argc, char *argv[], char *envp[]) {
/* envp is a null-terminated array of "KEY=VALUE" strings */
}
While environment variables are technically global state, they are at least explicitly set by the caller and conventionally read-only during a program's lifetime.
Lessons from the C and .NET Era
In C, global variables are particularly dangerous because there is no access control — any translation unit that includes the right header can read or write them. The static keyword limits a variable's visibility to a single file, which is one partial mitigation.
In the .NET world, static fields on classes serve a similar role to globals. The early .NET era saw many designs that abused static fields for shared state (singletons, service locators), only to discover the same coupling and testability problems that haunted C programs.
The lesson both eras reinforced: pass state explicitly through function parameters and return values. If many functions need the same data, consider grouping them and the data together in a class or struct. Dependencies should be visible in interfaces, not hidden in shared mutable state.
When a global truly cannot be avoided — for example, a process-wide logging facility or a signal handler flag — make it:
- Clearly documented with a comment explaining why it must be global
- As narrow in scope as possible (file-static rather than extern when feasible)
- Protected by appropriate synchronization if written from multiple threads
- Declared
volatileif modified from a signal handler or interrupt context