Flask's `g` - The "Global" That Isn't

Summary

Today I learned why Flask's request context object is called g, and it's too clever by half. It stands for global, but the joke is that it's request-local: each request gets its own isolated g, so you get global-like access without the race conditions a real shared global would hand you. Armin Ronacher chose it on purpose. It's quick to type, since you reach for it constantly, and it reads as obviously special, the same way e reads as an exception.

Today I learned why Flask's request context object is called g, and it's too clever by half.

I was reviewing a Flask codebase and kept seeing this pattern:

from flask import g

def get_db():
    if "db" not in g:
        g.db = sqlite3.connect("app.db")
    return g.db

I always assumed g was just an arbitrary short name. It stands for "global," but with a twist, and the twist is the joke. g gives you global-like access while being request-local. Each request gets its own isolated g instance.

Why not use an actual global? Because that's a mess:

# DON'T DO THIS - Shared between ALL requests!
db_connection = None

@app.route("/users/<user_id>")
def get_user(user_id):
    global db_connection
    if not db_connection:
        db_connection = sqlite3.connect("app.db")
    # Race conditions! Data leaks! Connection errors!

Multiple concurrent requests compete for the same connection, and you end up mixing user data or crashing outright.

g gives you what feels like a global but is isolated per request:

@app.route("/users/<user_id>")
def get_user(user_id):
    db = get_db()  # Gets THIS request's connection
    # Safe, isolated, no interference with other requests

The lifecycle is simple. A request arrives and Flask creates a fresh g object. You store stuff in it (g.db = connection), and you can reach that stuff anywhere in the request (return g.db). When the request ends, g is destroyed and cleanup runs.

Armin Ronacher, Flask's creator, chose g deliberately. It's a subtle joke about what developers think they want (global state) versus what they actually need (request-scoped state). The single letter is quick to type, which matters because you use it constantly, and it reads as obviously special, the same way e reads as an exception.

Here's a real pattern I now use for managing multiple resources:

from flask import g

def get_redis():
    if "redis" not in g:
        g.redis = redis.StrictRedis()
    return g.redis

def get_user():
    if "user" not in g:
        g.user = load_user_from_token()
    return g.user

@app.teardown_appcontext
def cleanup(error):
    # Always runs, even if request fails
    if hasattr(g, "redis"):
        g.redis.close()

So g is "global" only in the sense that your request-handling code can reach it from anywhere. Underneath, it's thread-safe, request-isolated storage wearing a global's clothes.