The previous article on variable scope in Python introduced the LEGB rule and the four scope levels that Python uses to resolve variable names. Now you need the practical details: how local and global variables actually behave in everyday code, what happens when they collide, and how to make intentional decisions about where each variable should live. These two scope levels, local and global, account for the vast majority of scope-related decisions you will make as a Python programmer, and understanding their interaction is what separates code that is easy to reason about from code that surprises you with unexpected behavior.
A local variable is any variable assigned inside a function body. Its name exists only while that specific function call is active, and it evaporates when the function returns. A global variable is assigned at the top level of a Python module, outside any function, and it persists for the entire lifetime of the program. The distinction sounds simple, but the edge cases around reassignment, mutable objects, and accidental shadowing trip up beginners and experienced developers alike. This article walks through each scenario with concrete examples so you can recognize and avoid these pitfalls before they become bugs.
How Python decides if a variable is local
Python does not determine a variable's scope based on where it is first used. Instead, it makes the decision at compile time based on whether the variable is assigned anywhere inside the function body. If a function contains an assignment to a variable name, Python marks that name as local for the entire function, even if the assignment appears after a line that tries to read the variable. This compile-time decision is the root cause of the UnboundLocalError that confuses many beginners:
message = "global"
def show():
print(message) # Tries to read message...
message = "local" # ...but Python already marked it local!The assignment on the second line of the function body causes Python to treat message as a local variable for the entire function. When the print line tries to read message, the local variable has not been assigned a value yet, so Python raises an UnboundLocalError rather than falling back to the global message variable. The fix is to either remove the assignment if you want to read the global, or add the global keyword before the first use if you intend to modify the global.
This compile-time scoping rule means you cannot have a variable be global for the first half of a function and local for the second half. The entire function body shares one consistent scope for each variable name, and Python enforces this strictly. Most linters will warn you when a variable is used before assignment within a function, which catches this class of bug before runtime.
Reading global variables from inside a function
When a function reads a global variable without trying to reassign it, Python's LEGB lookup finds the global after checking the local scope and finding nothing. This works cleanly and is the standard pattern for accessing configuration constants. If you define API_TIMEOUT = 30 at the module level and write a function that prints it, Python finds the value in the global scope because no assignment to API_TIMEOUT exists inside the function body. This is convenient for shared constants that genuinely apply across an entire module. However, relying on this pattern for variables that change during execution creates invisible dependencies between your functions. A reader of fetch_data cannot tell from its signature that it depends on a global timeout value, and changing that timeout for one test might unexpectedly affect other tests that call the same function.
The cleanest alternative when a function needs configurable behavior is to accept the configuration as a parameter with a default value. This makes the dependency explicit in the function signature and allows callers to override the default when needed without modifying global state. The parameter-based approach also makes the function self-documenting: anyone reading the signature can see that a timeout is involved and what its default value is.
Reassigning global variables with the global keyword
When a function needs to change the value of a global variable, rather than just read it, the global keyword is required. Without it, Python treats the assignment as creating a local variable, and the global remains unchanged. The global keyword must appear on its own line before the variable is used in any way:
visit_count = 0
def record_visit():
global visit_count
visit_count = visit_count + 1The global keyword tells Python to use the module-level visit_count rather than creating a local one. Without that declaration, the assignment on the following line would create a local visit_count, and the right side of the assignment would try to read that local variable before it had a value, producing the UnboundLocalError described earlier. The global keyword solves both problems: it tells Python where to find visit_count and allows both reading and writing to the module-level variable.
Global variables that change during program execution create challenges for testing and debugging. A failing test might be caused by a global being set to an unexpected value by a different test that ran earlier, and tracing the sequence of modifications through a large codebase is tedious and error-prone. For this reason, global variables that change at runtime should be rare, and when they are necessary, they should be clearly documented with comments explaining which functions are allowed to modify them and under what conditions.
Modifying mutable global objects
A subtle but important distinction: the global keyword is only needed to reassign the variable name itself, not to modify the object that the variable points to. If a global variable holds a mutable object like a list, dictionary, or set, a function can call methods on that object without the global keyword:
log_entries = []
def add_log(message):
log_entries.append(message) # Modifies the list, no global neededThe append call modifies the list object that log_entries refers to, but it does not reassign the name log_entries to point to a different object. Python treats method calls on objects differently from name reassignment, and the LEGB lookup for log_entries finds the global variable and uses the object it references. This behavior is consistent across all mutable types: dictionary updates, set additions, and list modifications all work without the global keyword as long as you are modifying the existing object rather than replacing it with a new one.
This distinction is important because many beginner resources teach that "you need global to modify a global variable," which is only true for reassigning the name. Functions that maintain a shared list of results, accumulate data into a global dictionary, or track state through a mutable collection often work correctly without the global keyword, and understanding why they work prevents both unnecessary global declarations and confusion when the pattern does not behave as expected.
Shadowing: when local and global names collide
Using the same variable name for both a local and a global variable is called shadowing, and while Python allows it, the results can be confusing. Inside the function, the local name takes priority and the global becomes inaccessible. Outside the function, only the global exists. The two variables with the same name are completely independent and can hold different values of different types:
count = 10
def process():
count = 5 # Creates a local count, shadows the global
print(count) # Prints 5
process()
print(count) # Prints 10, the global is unchangedThe function's local count is a separate variable from the module-level count. Assigning 5 to the local count has no effect on the global count, and printing count outside the function shows that the global retains its original value of 10. This independence is what makes functions safe containers for local state, but the shared name can mislead someone reading the code into thinking the two variables are connected.
The best practice is to avoid shadowing entirely. Use distinct names for global and local variables so that reading a variable name anywhere in your code unambiguously identifies which scope it belongs to. If a global constant is named MAX_RETRIES, do not create a local variable also called max_retries inside a function. The ALL_CAPS convention for global constants helps enforce this separation visually, and most linters will warn about shadowing of built-in names and global variables.
Practical guidelines for choosing local versus global
Default to local variables for any data that is only needed during the execution of a single function. Counter variables, intermediate calculation results, temporary lists built from parameters, and status flags that control the function's internal logic should all be local. Local variables are self-cleaning: they are created when the function is called and destroyed when it returns, so they cannot leak memory or cause unexpected interactions between calls.
Use global constants, marked with ALL_CAPS names, for configuration values that genuinely need to be consistent across an entire module. Database connection strings, API base URLs, file path templates, and numeric thresholds that control program behavior are common examples. These should be treated as read-only after the module is loaded, and no code should reassign them during normal program execution. The ALL_CAPS convention communicates this intent to other developers, even though Python cannot enforce it.
Use mutable global objects sparingly and with clear ownership. A global list that accumulates log messages might be acceptable in a small script where the alternative, threading a logger object through every function call, adds more complexity than the global introduces. In larger programs, mutable global state should be replaced by explicit state management: pass state into functions that need it, return updated state from functions that modify it, and keep the top-level coordination logic in a main function rather than scattered across module-level code. The article on writing reusable Python functions explores this explicit state management pattern in more detail.
Rune AI
Key Insights
- Local variables exist only inside a function and are destroyed when the function returns; global variables exist at the module level and persist for the program's lifetime.
- A function can read a global variable without the global keyword but must use global to reassign it.
- Modifying a mutable global object like a list does not require the global keyword, only reassigning the variable name does.
- Shadowing a global variable with a local one of the same name is allowed but confusing; use distinct names instead.
- Prefer passing values through parameters and return statements over relying on global state.
Frequently Asked Questions
What happens if I use the same variable name inside and outside a Python function?
Can a Python function modify a global list or dictionary without the global keyword?
How do I decide whether a variable should be local or global?
Conclusion
Local and global variables serve different purposes, and understanding their boundaries is what lets you write functions that are safe, predictable, and easy to debug. Keep variables local by default, use global constants sparingly, and reach for the global keyword only when you have exhausted cleaner alternatives like passing values through parameters and return statements.
More in this topic
Define and Call Python Functions
Learn the exact syntax for defining and calling Python functions with the def keyword, including naming rules, the function body, and how the call stack works.
Python Function Parameters and Arguments
Learn the difference between parameters and arguments, how to pass values by position and by keyword, and the rules for default parameter values.
Python Functions Explained
Learn what Python functions are, why they matter for organizing code, and how they form the foundation of non-trivial programs.