You’ve stepped into one of the most fascinating and mind-bending corners of Python! 🌀 Today, we’ll explore Closures — those clever, self-contained mini-tools that remember things even when their creator is long gone.

Think of closures as enchanted backpacks 🎒 that carry a little memory of their maker — ready to be opened anytime, anywhere.


🧠 What Exactly Is a Closure? (The Backpack Trick)

Let’s imagine you pack a backpack with some snacks 🍫 and a notebook 📓. You zip it up and hand it to a friend. Even after you leave, your friend can still open the backpack and enjoy what you packed inside.

That’s exactly what happens in a Python closure.

🎯 In code terms:

  • The backpack → an inner function (a function defined inside another function).
  • The snacks → variables from the outer function’s scope.
  • The friend → the inner function that still remembers those variables after the outer function finishes running.

Let’s unpack that with code.


💻 A Simple Python Closure Example

def outer():
    count = 0

    def inner():
        nonlocal count
        count += 1
        return count

    return inner

Here’s what’s happening:

  • outer() is our backpack-packer.
  • Inside it, we define count = 0.
  • The inner() function adds 1 to count and returns it.
  • But instead of calling inner() inside outer(), we return it — handing off the backpack!

Now watch the magic:

counter = outer()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

Even though outer() has finished running, the counter() function remembers the count variable. That memory — that connection — is what makes it a closure.


🤖 A Closure is a Function with a Memory

Definition: A closure is a function that:

  • Is defined inside another function,
  • References variables from that enclosing function,
  • And remembers those variables even after the outer function has finished executing.

Closures are like tiny data-holding machines — each one carrying its own state.


🎮 Real-Life Example: A Game Score Tracker

Suppose you’re building a small game and want to track players’ scores — but you don’t want to use global variables. Closures are perfect for this!

def create_score_tracker():
    score = 0

    def add_points(points):
        nonlocal score
        score += points
        return score

    return add_points

Usage:

player1 = create_score_tracker()
print(player1(10))  # 10
print(player1(5))   # 15

player2 = create_score_tracker()
print(player2(7))   # 7

Each player gets their own private closure, with their own score safely tucked inside. No interference. No globals. Just clean, self-contained logic.


🛑 When nonlocal Doesn’t Play Nice

Sometimes, especially in older interpreters or limited environments (like some online Python playgrounds), you might run into this:

SyntaxError: no binding for nonlocal 'count' found

That usually happens if:

  • The variable isn’t in the immediate outer scope, or
  • The environment doesn’t handle nonlocal properly.

🩹 Quick Fix: Use a Mutable Object (like a list)

def outer():
    count = [0]

    def inner():
        count[0] += 1
        return count[0]

    return inner

Now:

counter = outer()
print(counter())  # 1
print(counter())  # 2

The list works because it’s mutable — meaning we can change its content without redefining it.


🧃 Reading vs. Updating Outer Variables

You can read outer variables inside a closure just fine:

def outer():
    name = "Ekene"

    def inner():
        return "Hello, " + name

    return inner

But if you try to change that variable:

def outer():
    name = "Ekene"

    def inner():
        name += " the Coder"  # ❌ Error!
        return name

    return inner

You’ll get:

UnboundLocalError: local variable 'name' referenced before assignment

Python assumes you’re trying to create a new local variable called name — not modify the outer one.


🧩 Why nonlocal Matters

When you add nonlocal, you’re telling Python:

“Hey, I don’t want a new local variable — I want to modify the one from the outer function.”

Without nonlocal:

def outer():
    count = 0

    def inner():
        count += 1  # ❌ Error
        return count

    return inner

With nonlocal:

def outer():
    count = 0

    def inner():
        nonlocal count
        count += 1
        return count

    return inner

Now the closure works perfectly — and your function remembers and updates its state each time it’s called.


🧭 Summary

Closures let you create functions that remember things — a perfect mix of data and behavior in one neat package. They make your code elegant, reusable, and delightfully self-contained.

If you ever feel like your functions are forgetting too much, hand them a backpack — give them a closure! 🎒💡


📝 Review & Practice — Fill in the Gaps

  1. A closure is a function that ____ and ____ even after the outer function finishes running.
  2. In a closure, the outer function’s variables act like items packed into a ____.
  3. The nonlocal keyword allows the inner function to ____ variables from the outer function.
  4. If you try to modify an outer variable without using nonlocal, Python raises a ____ error.
  5. True or False: A closure can remember its outer function’s variables even after that outer function is gone.
  6. Write a function create_timer() that returns another function which adds 1 second each time it’s called, without using nonlocal.
  7. Why might we replace an integer with a list in a closure? (Hint: mutability!)
  8. What will this code print?

    def outer():
        name = "Ekene"
        def greet():
            return "Hello, " + name
        return greet
    
    say_hello = outer()
    print(say_hello())
    
  9. When do we use nonlocal instead of global?
  10. In your own words, describe one real-world situation where you could use closures.

<
Previous Post
🧭 JavaScript Navigator Object: Your JavaScript Passport to the Browser
>
Next Post
🚀JavaScript BOM (Browser Object Model): Your JavaScript Spaceship Dashboard