PROGRAMMING-CONCEPTS

Closure: Definition, Purpose, and Examples

A closure is a programming feature where a function remembers the variables from the environment in which it was created — even after that outer function has finished running.

This makes closures powerful for tasks like data privacy, lightweight state management, function factories, and event handling.

Closures appear constantly in JavaScript and Swift, and they’re widely used in Python as well. Once you understand how a function can “carry its backpack of variables” around, many advanced code patterns begin to make sense.


What a Closure Really Is

A closure forms when three things are present:

  1. A function
  2. An outer scope with variables
  3. The inner function uses those variables

When the inner function is returned or passed elsewhere, it keeps access to the outer variables.

Think of it like a small function carrying a suitcase of values it captured earlier.

Closures allow:

  • private data
  • persistent state
  • custom pre-configured functions
  • callbacks with memory

Closures in JavaScript and TypeScript

JavaScript uses lexical scoping, meaning functions know the variables that were in scope when they were created — not when they are called. This is the foundation of closures.


Example 1: A Function That Remembers Its Base Value

function multiplier(factor) {
  return function (n) {
    return n * factor;
  };
}

const triple = multiplier(3);
triple(10); // 30

The returned function “remembers” the factor even after multiplier() has finished.

This makes it easy to create specialized functions from general ones.


Example 2: Private Variables with Closures

function createCounter() {
  let count = 0;

  return {
    next() {
      count += 1;
      return count;
    }
  };
}

const c = createCounter();
c.next(); // 1
c.next(); // 2

The variable count is private — only the returned functions can access or modify it.

This simulates encapsulation without classes.


Example 3: Event Handlers Using Closures

function makeHandler(message) {
  return () => console.log(message);
}

document.querySelector("#save")
  .addEventListener("click", makeHandler("Saving..."));

Even though the event fires much later, the handler still knows the correct message.


Closures in Python

Python functions also capture their surrounding scope, though Python emphasizes clarity about where values come from.


Example 1: Function Generator

def power(exponent):
    def apply(n):
        return n ** exponent
    return apply

square = power(2)
square(9)  # 81

square holds onto exponent=2 because it was created inside that environment.


Example 2: Tracking State Across Calls

def logger():
    logs = []

    def record(entry):
        logs.append(entry)
        return list(logs)

    return record

write = logger()
write("Start")
write("Processing")

logs persists between calls through the closure.


Important Note: Python’s Late Binding Behavior

If closures use loop variables, Python captures the variable, not the value at that moment.

This sometimes requires default arguments to fix:

funcs = []
for i in range(3):
    funcs.append(lambda x=i: x)

[f() for f in funcs]  # [0, 1, 2]

This pattern prevents unwanted shared state.


Closures in Swift

Swift closures are heavily used for callbacks, animations, sorting functions, and asynchronous code.

A closure in Swift is similar to JavaScript’s concept: it captures values from its surrounding context.


Example 1: Basic Swift Closure Capturing a Value

func makeIncrementer(step: Int) -> () -> Int {
    var total = 0
    return {
        total += step
        return total
    }
}

let inc = makeIncrementer(step: 5)
inc() // 5
inc() // 10

The closure keeps its own total, even though makeIncrementer has already returned.


Example 2: Capturing Values Inside Async Callbacks

func loadData(url: String, completion: @escaping (String) -> Void) {
    // ... networking ...
    completion("Loaded: \(url)")
}

loadData(url: "index.json") { result in
    print(result)   // still knows about `result`
}

The closure used in completion: remembers variables available at the moment it was created.


Example 3: Capturing Self

Swift requires explicit self inside closures to make ownership clear:

class Tracker {
    var points = 0

    func begin() {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            self.points += 1
        }
    }
}

This prevents accidental memory cycles and enforces safe design.


Why Closures Matter

Closures are essential for:

  • maintaining state without classes
  • customizing behavior by embedding configuration
  • building factories (functions that generate functions)
  • event-driven programming
  • functional programming patterns
  • safe encapsulation
  • callbacks and asynchronous code
  • memoization and caching
  • modular code design

Many real-world APIs rely entirely on closures for clean, expressive workflow.


Real-World Examples

Fresh examples from common programming tasks.


Example 1: Configurable Validation (JavaScript)

function minLength(len) {
  return function (value) {
    return value.length >= len;
  };
}

const check10 = minLength(10);
check10("short");       // false
check10("long enough"); // true

This pattern avoids rewriting multiple validation functions.


Example 2: Incremental Processor (Python)

def collector(prefix):
    items = []

    def add(text):
        items.append(f"{prefix}: {text}")
        return items

    return add

log = collector("INFO")
log("Starting")
log("Done")

Each closure holds its own list and prefix.


Example 3: Swift’s Animations (UIKit)

UIView.animate(withDuration: 0.4) {
    view.alpha = 0.0
}

The closure captures the view reference so the animation knows which element to update later.


Common Mistakes and How to Avoid Them

Accidental Shared State

Creating closures inside loops without capturing the current value leads to confusing bugs (Python’s classic late-binding issue).

Memory Leaks in Swift

Closures capturing self can create strong reference cycles—use [weak self] when appropriate.

Confusing Scope Access

Beginners sometimes expect closures to use new values when the environment changes. Closure values are frozen at creation time (except JS objects mutated later).

Overusing Closures

Using closures where simple functions suffice can reduce readability.


Best Practices

  • Keep closures short and focused
  • Avoid capturing too many external variables
  • Use closures for configuration or lightweight state
  • In Swift, use [weak self] when needed to avoid retain cycles
  • In Python, use default arguments to capture loop values safely
  • In TypeScript, type your closure parameters explicitly

Summary

A closure forms when a function remembers variables from the environment where it was created. JavaScript closures power event handlers, React hooks, and function factories. Python uses closures for memoization, configuration, and lightweight state. Swift closures appear everywhere from animations to callbacks.

Closures allow code to be expressive, modular, and dynamic. Once you understand how they capture and retain values, you unlock a huge range of modern programming patterns.

Learn to Code for Free
Start learning now
button icon
To advance beyond this tutorial and learn to code by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

Reach your coding goals faster