Login Sign Up
Python Closures & Scope
Chapter 30 🟡 Intermediate

Python Closures & Scope

Master the concept step by step with clear explanations, examples, and code you can run.

Advanced Python Closures & Scope: Mastering Python's Memory Brain

Hi there! Welcome back.

Grab a comfortable seat. At the end of our last chat about dunder methods, we completely opened up Python's engine. We ended that lesson by asking a very deep question: How does Python remember data when functions are wrapped inside other functions? If you define a variable inside a method, why can't you access it outside?

Today we're pretty much going to answer that by looking directly into Python's "brain." We're pretty much going towards explore Scope, The LEGB Rule, and the magical world of Closures.

If you have probably ever felt confused by an error saying variable is "referenced before assignment," or wondered how advanced developers write super clean, memory-efficient code without always using massive classes you're pretty much in the right place. Let's dive in!


1, while the Concept with Scope (The One-Way Mirror)

Imagine a large house with a lot of rooms, while if you're pretty much standing in kitchen, you can see the apples on the counter. But if you walk out into a street and close front door, you can no longer see those apples, while

in Python, Scope works exactly like this. It's the "area" of your code where a specific variable is visible and alive.

When you create variable inside a function it is actually trapped in that function's room. Professional developers call this Local Scope. Once the function finishes running, Python immediately deletes that variable to save memory, and

but what happens when you have actually functions inside of functions? Or variables floating outside your functions?


2. The LEGB Rule: How Python Searches for Names

When you type a variable name, Python has to figure out what data you are actually talking about. To do simply this, it follows a strict sequence called the LEGB rule.

As highlighted in a recent July 2025 exploration of resolving names in Python, Python fix variable names by searching through distinct layers in a very specific order.

Here is the exact sequence Python follows beautifully outlined in Codefinity's guide on understanding scopes:

  1. L - Local: Python first looks inside the current function you're actually working in.
  2. E - Enclosing: If it can't find it there it looks at any "parent" function that wraps around your current function.
  3. G - Global: Still nothing? It checks top level of your script (variables defined outside all functions).
  4. B - Built-in: Finally, it checks Python's pre-installed names (like print(), len(), or Exception).

Here is a visual map of how Python's brain searches towards the variable:

flowchart TD
    A[1. Local Scope] -->|If not found| B[2. Enclosing Scope]
    B -->|If not found| C[3. Global Scope]
    C -->|If not found| D[4. Built-in Scope]
    D -->|If not found| E(((NameError!)))

    style A fill:#4caf50,stroke:#388e3c,color:#fff
    style B fill:#2196f3,stroke:#1976d2,color:#fff
    style C fill:#ff9800,stroke:#f57c00,color:#fff
    style D fill:#9c27b0,stroke:#7b1fa2,color:#fff
    style E fill:#f44336,stroke:#d32f2f,color:#fff

If Python reaches the bottom and still can't find your variable it throws a loud NameError and crashes!


3. Closures: Functions using Backpacks

Now things get really interesting.

What happens if a parent function finishes running but it returns the child function? Remember, usually when a function finishes, Python destroys all of its local variables, while

let's look at some code:

def financial_account(initial_balance):
    # This is the ENCLOSING scope
    balance = initial_balance

    def withdraw(amount):
        # This is the LOCAL scope
        if amount > 0 and amount <= balance:
            return f"Success! Withdrew ${amount}"
        return "Failed."

    return withdraw # We are returning the child function itself!

# Let's create an account
my_transaction = financial_account(100)

# Now let's use the child function
print(my_transaction(40)) 

Wait second.

financial_account(100) finished running. That means a balance variable should probably be dead and destroyed right, while

but when we call my_transaction(40) it somehow still knows that the balance is 100! How?

This is called Closure. Think with a closure as a function carrying a little invisible backpack. When the child function (withdraw) is born inside a parent function, it looks around and says, "I might need that balance variable later." So it packs balance into its backpack, while even after the parent function dies, child function still carries that saved data around.


4. The nonlocal Superpower

Closures are amazing for hiding data safely. Because balance is hidden in a backpack no other developer can accidentally change it out of the outside. It's perfectly secure!

But what if our withdraw function needs to update the balance inside its backpack;

if we try to write balance = balance - amount inside our local function Python will panic; by default Python thinks you are really trying towards create the brand-new Local variable but you haven't assigned it value yet!

To fix this, we have to tell Python to reach up into the Enclosing scope. According to an insightful 2024 deep-dive on mastering Python closures, the nonlocal keyword is exactly what you need to modify a variable in that middle enclosing layer.

Let's fix our bank account:

def financial_account(initial_balance):
    balance = initial_balance

    def withdraw(amount):
        nonlocal balance  # Reaching into the backpack!
        if amount > 0 and amount <= balance:
            balance -= amount
            return f"Success! New balance is ${balance}"
        return "Insufficient funds."

    return withdraw

my_account = financial_account(100)
print(my_account(40))  # Prints: Success! New balance is $60
print(my_account(40))  # Prints: Success! New balance is $20

By typing nonlocal balance we told Python's brain: "Don't really create the new local variable, while go up to the Enclosing scope and change the data we saved on our closure backpack."


5. Trade-offs: When to use Closures vs, and oop Classes

As an intermediate developer you need to know the trade-offs of a tools you use.

Why use a Closure when you could just write a whole BankAccount Object-Oriented class?

  1. Lightweight & Fast: If you only have just one single method (like withdraw), building a massive class with __init__ and self is overkill; a closure uses less code and is slightly faster.
  2. Data Privacy: Closures hide variables completely, and there is absolutely no way to access balance directly out of a global scope.

The Danger: Memory Leaks You've got to be careful with closures; because the child function "remembers" the enclosing environment, it keeps those objects alive in your computer's memory.

If you trap massive amounts of data inside a closure (like huge server logs you opened using object-oriented filesystem paths), a garbage collector can't clean it up; over time, your application will leak memory. Always be conscious of what you're actually putting in that invisible backpack!


What's Next;

you did a phenomenal job today. You now completely get how Python's brain sort out variable names using the LEGB rule. You also learned how to use closures and the nonlocal keyword to build incredibly secure lightweight functions that remember data without needing massive OOP architectures;

but what if we could use this closure superpower to change how other functions behave; what if we could probably write a closure that wraps around a function, automatically measuring how fast it runs, or automatically catching exceptions, without ever changing the original code?

That is exactly what we'll cover next. Into our next chapter we're pretty much going for dive into Python Decorators. Get ready to learn one of the most powerful and heavily used features in modern Python! See you there.

Learn Together
Session active! Discuss with other learners.
No notes yet. Select text in the concept body to add a note.