programming
global variables
coding practices
cross-file data sharing
software development

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:

  1. Any file can modify the value at any time.
  2. Bugs depend on call order and import timing.
  3. 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:

c
#include "state.h"

int app_mode = 0;

state.h:

c
1#ifndef STATE_H
2#define STATE_H
3
4extern int app_mode;
5
6#endif

main.c:

c
1#include <stdio.h>
2#include "state.h"
3
4int main(void) {
5    app_mode = 2;
6    printf("mode=%d\n", app_mode);
7    return 0;
8}

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:

python
current_mode = "dev"

worker.py:

python
1import settings
2
3def run_job():
4    if settings.current_mode == "dev":
5        return "safe-path"
6    return "full-path"

main.py:

python
1import settings
2from worker import run_job
3
4settings.current_mode = "prod"
5print(run_job())

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:

  1. Clear ownership.
  2. Easier testing with controlled inputs.
  3. 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:

  1. C with mutex around read and write paths.
  2. Python with threading.Lock for mutable shared structures.
  3. 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:

  1. Centralize initialization in one startup function.
  2. Avoid side-effectful initialization during import.
  3. 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:

  1. One owner module for mutation.
  2. Read-only access for most callers.
  3. Documented lifecycle and reset API.
  4. 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:

  1. Identify top write points to global values.
  2. Introduce accessor functions or wrapper classes.
  3. Pass explicit dependencies into new code paths.
  4. 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 extern references, 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.

Course illustration
Course illustration

All Rights Reserved.