A nested function is a function defined inside another function. The inner function exists only within the body of the outer function and cannot be called from outside it. This might sound like a limitation, but it is exactly the property that makes nested functions useful: they encapsulate helper logic that belongs exclusively to one function, keeping the module namespace clean and the code's structure explicit about which parts belong together.
The articles on variable scope in Python and local and global variables in Python introduced the enclosing scope, the second level in Python's LEGB lookup that sits between local and global. Nested functions are where the enclosing scope becomes practically useful. An inner function can read the outer function's variables without any special syntax, and with the nonlocal keyword it can reassign them. This access pattern powers both the helper function pattern and the closure pattern, two of the most important use cases for nesting.
Helper functions that stay private
The most straightforward use of nested functions is to hide helper logic that is only relevant to one function. If a function needs to perform a subtask that is too complex to inline but too specific to export as a module-level function, nesting that subtask inside the function keeps it private and makes its purpose obvious:
def validate_form(data):
def is_valid_email(email):
return "@" in email and "." in email.split("@")[-1]
errors = []
if not is_valid_email(data.get("email", "")):
errors.append("Invalid email address")
return errorsThe is_valid_email function exists only inside validate_form. No other code in the module can call it, which is exactly right because email validation in this context is specific to form validation and may differ from email validation elsewhere in the application. The nesting makes the relationship explicit: is_valid_email serves validate_form and nothing else. If email validation later becomes useful in multiple places, it can be lifted to the module level with a more descriptive name, but until then, nesting keeps it contained.
Helper functions also benefit from accessing the outer function's variables directly. A helper that processes elements from a list that the outer function is iterating over can read that list by name without receiving it as a parameter, which reduces argument-passing boilerplate for genuinely internal helpers. The tradeoff is that the helper becomes tightly coupled to the outer function's variable names, which is acceptable when the helper is exclusively used by that one function.
Function factories that produce customized functions
An outer function can define and return an inner function, creating a factory pattern. The outer function accepts configuration parameters, and the inner function it returns is customized to use those parameters. Each call to the outer function produces a new function with its own set of captured values:
def make_power(exponent):
def power(base):
return base ** exponent
return power
square = make_power(2)
cube = make_power(3)
print(square(5)) # 25
print(cube(5)) # 125The make_power function accepts an exponent and returns an inner function that raises its argument to that exponent. The variable exponent is in the enclosing scope of the inner power function, and power retains access to it even after make_power returns. Each call to make_power creates a new enclosing scope with its own exponent value, so square and cube are independent functions with different captured exponents. This is the factory pattern made possible by nested functions and the enclosing scope.
The factory pattern is more readable than the equivalent solution using a class with a single method, and it is more flexible than a solution using default arguments because the captured value is truly private. No caller of the returned function can inspect or modify the captured exponent because it lives in the enclosing scope, which is inaccessible from outside the function. The article on closures in Python explores this pattern in greater depth, including how closures differ from simple nested functions and when each is the right choice.
Using nonlocal to modify enclosing variables
An inner function can read enclosing variables freely, but reassigning them requires the nonlocal keyword, just as reassigning global variables requires the global keyword. Without nonlocal, Python treats an assignment as creating a new local variable inside the inner function, leaving the enclosing variable unchanged:
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2The nonlocal keyword tells Python that count belongs to the enclosing scope, not to increment's local scope. Without nonlocal, the line count += 1 would create a local count inside increment and raise an UnboundLocalError because the local count has not been assigned a value before the += reads it. With nonlocal, the increment modifies the enclosing count, and each call updates the same variable. This pattern creates functions with private mutable state without using classes or global variables.
The nonlocal keyword searches enclosing scopes from innermost to outermost and modifies the first match it finds. If multiple levels of nesting exist and each level defines a variable with the same name, nonlocal targets the innermost enclosing scope that contains that name. This is consistent with how Python resolves variable names for reading, and it means nonlocal works predictably even in deeply nested functions, though deeply nested functions are rare in well-structured code.
When to avoid nesting
Nested functions create a dependency between the inner and outer functions that makes both harder to test in isolation. A nested helper cannot be unit tested directly because it is not accessible from outside the outer function. If the helper contains complex logic that needs thorough testing, consider extracting it to a module-level function that accepts all its dependencies as parameters. The tradeoff is between encapsulation, keeping the helper private to its caller, and testability, making the helper independently verifiable.
Deeply nested functions, where a function is defined inside a function that is itself inside another function, are a readability problem. Each level of nesting adds indentation and forces the reader to keep track of which scope each variable belongs to. Two levels of nesting, an outer function and an inner helper or factory, is common and readable. Three or more levels suggest that the code should be restructured, possibly by extracting some of the inner functions to module-level functions with clear names.
If the inner function does not access any variables from the enclosing scope and does not need to be private, it should be a module-level function. Nesting for the sake of nesting adds indentation and cognitive overhead without providing any of the benefits of encapsulation or state capture. A function that stands alone at the module level is easier to find, import, test, and reuse than one buried inside another function's body.
Rune AI
Key Insights
- A nested function is defined inside another function and is only accessible within that outer function's body.
- Nested functions can read variables from the enclosing scope; use the nonlocal keyword to reassign them.
- Nested functions are useful for private helpers, function factories, and closures that carry state.
- If a nested function is useful in multiple places, lift it to the module level and give it a clear name.
- Deeply nested functions (more than two levels) are hard to read and should be refactored.
Frequently Asked Questions
Why would I define a function inside another function in Python?
Can a nested function access variables from the outer function?
Can a nested function modify variables from the outer function?
Conclusion
Nested functions are a tool for organizing code when a piece of logic belongs exclusively to one function and should not be exposed to the rest of the module. They enable helper functions that are truly private, factory functions that produce customized callables, and closures that capture state. Use them when the inner function's purpose is inseparable from the outer function, and lift them to the module level when they become useful across multiple callers.
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.