Using global variables between files?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Sharing global variables across files can work, but it often creates hidden dependencies that are hard to test and maintain. Different languages support this pattern in different ways, and each has tradeoffs around initialization order, thread safety, and clarity. This guide explains safe usage and better alternatives.
Core Topic Sections
What cross-file global state means
A global variable shared across files is a single value accessible from multiple modules. Typical use cases include configuration flags, counters, and shared caches.
Main risks:
- Any file can modify the value at any time.
- Bugs depend on call order and import timing.
- Unit tests can leak state between test cases.
Because of these risks, treat globals as a last resort.
C example with extern
In C, define global storage in one source file and reference it with extern in others.
state.c:
state.h:
main.c:
This compiles and works, but write access is global unless constrained by design.
Python module-level state pattern
Python modules are singletons per interpreter process, so module variables are effectively shared state across imports.
settings.py:
worker.py:
main.py:
This is simple, but hidden writes across modules can still become fragile.
Prefer explicit dependency passing
A safer architecture passes shared values as arguments or encapsulates them in objects.
Benefits:
- Clear ownership.
- Easier testing with controlled inputs.
- Fewer side effects from unrelated code.
For large systems, dependency injection or context objects are usually better than global writes.
Thread safety considerations
When multiple threads can read and write shared globals, race conditions are possible. If shared mutable state is unavoidable, use synchronization primitives.
Examples:
- C with mutex around read and write paths.
- Python with
threading.Lockfor mutable shared structures. - Java with atomic types or synchronized access.
If coordination is complex, prefer message passing or immutable snapshots.
Initialization order and startup hazards
Cross-file globals can fail when one module expects initialized state before another module sets it. This appears as occasional startup bugs that are hard to reproduce.
Mitigation strategies:
- Centralize initialization in one startup function.
- Avoid side-effectful initialization during import.
- Add health checks that verify required state before serving traffic.
Deterministic initialization order is critical in production services.
Controlled global pattern
If globals are required, enforce rules:
- One owner module for mutation.
- Read-only access for most callers.
- Documented lifecycle and reset API.
- Test helper to clear state between tests.
These constraints reduce accidental coupling while keeping implementation simple.
Migration path away from globals
When refactoring legacy code:
- Identify top write points to global values.
- Introduce accessor functions or wrapper classes.
- Pass explicit dependencies into new code paths.
- Remove direct module-level writes gradually.
Incremental migration lowers risk compared with full rewrite.
Common Pitfalls
- Declaring many writable globals and losing track of who changes what.
- Using cross-file globals in tests without reset logic between test cases.
- Ignoring thread safety when globals are mutable.
- Relying on import side effects for initialization order.
- Treating global state as convenience rather than explicit design decision.
Summary
- Cross-file global variables are possible but increase coupling and maintenance risk.
- C uses
externreferences, while Python shares module-level state by import. - Prefer explicit dependency passing and controlled ownership of shared state.
- Address thread safety and initialization order early.
- If globals remain, enforce strict mutation and testing rules.

