The first time I wrote an event handler in Python, I passed a function to another function and could not figure out why it worked. The function I passed was not executed immediately – it was stored and called later when the event fired. That delay is what makes callbacks useful and initially confusing.

This article explains what callback functions are in Python, how they work, and where you encounter them without noticing. By the end, you’ll understand why sorted(), map(), and filter() all accept function arguments, and how to write your own functions that accept callbacks.

TLDR

  • A callback is a function passed as an argument to another function, to be called later
  • Built-in functions like sorted(), map(), and filter() accept callbacks via the key parameter
  • User-defined functions can accept callbacks just like built-in functions do
  • Closures and lambdas are commonly used as callback definitions inline
  • Callbacks are the foundation of event-driven programming in Python

What is a Callback Function in Python?

A callback is a function that is passed to another function as an argument, to be invoked at some later point in the program flow. The function receiving the callback decides when to call it – the callback does not execute immediately upon being passed.

This is possible because Python treats functions as first-class objects. A function can be assigned to a variable, stored in a data structure, returned from another function, or passed as an argument – just like any other value. This design is what makes callbacks possible.

A function becomes a callback when it satisfies one of these conditions:

  • It is passed as an argument to another function
  • It is defined inside another function and then returned or stored

Both conditions produce the same outcome – a function that gets called later by the receiving function. Whether the callback is a named function, a lambda, or a closure does not change the mechanism.

Using Callbacks with Built-in Functions

The most common place to encounter callbacks in Python is the sorted() function. Its key parameter accepts a function that transforms each element before comparison.


# Default ASCII-based sort
a = ["lmn", "AbCd", "khJH", "ert", "SuNnY"]
print(sorted(a))

# Sort by lowercase form of each string
print(sorted(a, key=str.lower))


['AbCd', 'SuNnY', 'ert', 'khJH', 'lmn']
['AbCd', 'ert', 'khJH', 'lmn', 'SuNnY']

In the first call, Python sorts strings by their ASCII values – uppercase letters come before lowercase ones. In the second call, str.lower is passed as the callback. The sorted() function calls str.lower on each string first, then sorts by the resulting values. The original strings remain unchanged; only their comparison values are modified.

The same pattern appears in map() and filter(). The map() function applies a callback to every element in an iterable and returns an iterator of the results. The filter() function applies a callback to every element and returns only those where the callback returns True.


nums = [1, 2, 3, 4, 5]

squared = list(map(lambda x: x ** 2, nums))
evens = list(filter(lambda x: x % 2 == 0, nums))

print(squared)
print(evens)

Both map() and filter() accept a function as their first argument and an iterable as the second. The function argument is called once per element – that function is the callback. Lambda expressions are a convenient way to define small callbacks inline without naming them separately.

Writing User-Defined Functions that Accept Callbacks

The mechanism works the same way in user-defined functions. A function can accept another function as a parameter and call it whenever appropriate.


def apply_operation(x, op):
    return op(x)

result = apply_operation(7, lambda n: n * 3)
print(result)

The apply_operation() function takes a value and a callback function. It calls the callback with the value and returns the result. The callback is not executed until apply_operation() decides to call it.

The callback does not have to be a lambda. It can be any callable – a named function, a lambda, or a callable object. Passing a named function is often clearer when the callback logic is complex enough to need a name.


def multiply_tuple(t):
    return t[0] * t[1]

def process_tuple(func, data):
    return func(data)

num = (8, 5)
answer = process_tuple(multiply_tuple, num)
print(f"Product: {answer}")

Here, multiply_tuple is defined as a named function and then passed to process_tuple(). The first function decides what operation to perform on the data; the second function decides when to call it. This separation is the core value of callbacks.

Callbacks with Closures

A closure is a function that captures variables from its enclosing scope. Closures are useful when you need a callback that carries some state with it, without using a class or global variables.


def make_multiplier(factor):
    def multiplier(x):
        return x * factor
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(7))
print(triple(7))
print(double(15))

make_multiplier() returns a closure that holds the factor argument in its scope. Each returned closure remembers its own factor value. This pattern is common in event handlers where different buttons need different behaviors but share the same underlying logic.

Real-World Example: Sorting with Custom Keys

Callbacks become more valuable when the sorting or processing logic is specific to your data. The example below sorts a list of records by a computed field – something no built-in comparison can handle directly.


students = [
    {"name": "Alice", "grade": 85},
    {"name": "Bob", "grade": 92},
    {"name": "Charlie", "grade": 78},
    {"name": "Diana", "grade": 91},
]

sorted_students = sorted(students, key=lambda s: s["grade"], reverse=True)

for s in sorted_students:
    print(f"{s['name']}: {s['grade']}")


Bob: 92
Diana: 91
Alice: 85
Charlie: 78

The key=lambda s: s["grade"] callback tells sorted() to extract the grade from each dictionary before comparison. The callback runs once per element during the sort process. Without the callback, Python would not know how to compare dictionaries, and the sort would fail or produce unexpected results.

FAQ

Q: What is the difference between a callback and a closure?

A callback is a function passed as an argument to another function. A closure is a function that captures variables from its enclosing scope. A callback can be a closure, but not all callbacks are closures. A closure always refers to variables that existed when the closure was created, regardless of whether it was passed as a callback.

Q: When should I use a lambda instead of a named function as a callback?

Use a lambda for simple, one-off transformations that do not need documentation. If the callback logic is complex, repeated, or benefits from a descriptive name, define a named function instead. Named functions produce better stack traces and are easier to test independently.

Q: Can a single function accept multiple callbacks?

Yes. A function can accept any number of callback arguments. This pattern appears in middleware systems, event dispatchers, and data processing pipelines where different stages need to be configured independently.

Q: Are callbacks used in Python GUI programming?

Yes. GUI toolkits like tkinter use callbacks extensively. Button clicks, keystrokes, and window events are handled by functions passed to the toolkit. The tkinter command parameter of a button, for example, accepts a callback that gets executed when the button is clicked.

Q: Does Python have any built-in functions that use callbacks besides sorted(), map(), and filter()?

Yes. max() and min() accept a key callback with the same behavior as sorted(). The any() and all() functions accept any callable as their sole argument and apply it to each element of an iterable.

Callback functions are one of the mechanisms that make Python flexible enough to handle diverse data processing tasks with a consistent interface. Once the pattern clicks – passing a function to be called later – the syntax of sorted(key=func) and map(func, iterable) becomes obvious rather than magical.

Share.
Leave A Reply