Defining a function inside another function (nested functions or inner functions) is a powerful Python feature that lets you create helper functions scoped locally to the outer function — they can access variables from the enclosing scope (closures), stay hidden from the global namespace, avoid naming conflicts, and encapsulate logic that’s only relevant inside the outer function. Nested functions are commonly used for factories (returning customized functions), decorators, private helpers, and clean code organization. In 2026, nested functions remain essential — they enable closures for stateful callbacks, dynamic behavior in data pipelines, and modular, testable code in pandas/Polars transformations, ML workflows, web apps, and production systems.
Here’s a complete, practical guide to nested functions in Python: basic syntax, closures and scope, factory pattern, real-world patterns, and modern best practices with type hints, nonlocal, performance, testing, and pandas/Polars integration.
Basic nested function — inner function defined inside outer, can be called locally or returned.
def outer_function(x: int):
def inner_function(y: int) -> int:
return x + y # inner accesses outer's x (closure)
return inner_function
add_five = outer_function(5)
result = add_five(3)
print(result) # 8
Closures — inner function remembers outer scope variables even after outer returns.
def make_counter():
count = 0
def counter():
nonlocal count # modify outer variable
count += 1
return count
return counter
c1 = make_counter()
c2 = make_counter()
print(c1()) # 1
print(c1()) # 2
print(c2()) # 1 (separate closure)
Real-world pattern: nested functions in pandas/Polars pipelines — create reusable, scoped helpers for transformations or validation.
import pandas as pd
def process_dataframe(df: pd.DataFrame, threshold: float):
def normalize_column(col):
return (col - col.mean()) / col.std()
def clip_outliers(col):
return col.clip(lower=threshold, upper=1-threshold)
# Nested helpers scoped to this process
df = df.apply(normalize_column)
df = df.apply(clip_outliers)
return df
# Usage
data = pd.DataFrame({'A': [1, 2, 100, 4], 'B': [10, 20, 30, 40]})
cleaned = process_dataframe(data, 0.05)
print(cleaned)
Best practices make nested functions safe, readable, and performant. Use nested functions for private helpers — keep them local, avoid global namespace pollution. Prefer closures for stateful callbacks — make_counter, event handlers. Use nonlocal to modify outer variables — required in Python 3+. Modern tip: use Polars for lazy pipelines — nested expressions or custom functions for scoped transformations. Add type hints — def outer(x: int) -> Callable[[int], int] — improves clarity and mypy checks. Keep nesting shallow — deep nesting hurts readability; extract to separate functions if complex. Use @functools.lru_cache on returned functions — memoize expensive inner computations. Test nested functions independently — extract to top-level for testing if needed. Combine with decorators — wrap outer or inner for logging/timing. Avoid mutable defaults in nested functions — same pitfalls as top-level. Use contextlib.contextmanager with yield — nested context managers for resource handling.
Nested functions encapsulate local logic, create closures, and enable factories and dynamic behavior. In 2026, use them for scoped helpers, stateful callbacks, type hints, nonlocal, Polars expressions, and shallow nesting. Master nested functions, and you’ll write modular, testable, memory-safe Python code that keeps concerns cleanly separated.
Next time you need a private helper or customized function — define it inside. It’s Python’s cleanest way to say: “This function belongs only here — and it remembers its surroundings.”