🚀 Supercharge your YouTube channel's growth with AI.
Try YTGrowAI FreeCallback functions in Python – A Complete Overview

Passing notify gives deliver the callable. I’m curious why parentheses change the handoff: notify(“ready”) runs now and gives the receiver its return value.
Trace deliver(notify, “ready”) into the line where deliver invokes its callback. Then identify what “callback” names in that call.
What Is a Callback Function in Python?
A callback is a callable that one function receives and invokes as part of its work. Python functions are objects, so passing notify gives the receiver the function, while notify(“ready”) calls it now and gives the receiver its return value.
| Expression | What the receiver gets |
|---|---|
| deliver(notify, “ready”) | The notify function, which deliver calls with ready. |
| notify(“ready”) | The function’s return value, because notify runs before the outer call. |
A callback names the function’s role in a call, not a special Python syntax or separate function type.
Prerequisites for the Examples
These examples use Python 3 and built-in functions only. Save an example in a Python file or run it in a REPL to inspect what the callback returns and when a lazy iterator does its work.
- Use the Python interpreter available in your environment.
- No third-party package or GUI is required.
Step 1: Pass a Function Without Calling It
Pass notify without parentheses, then deliver calls it between the two printed messages.
def notify(value):
return f"received {value}"
def deliver(callback, value):
print("before callback")
result = callback(value)
print("after callback")
return result
reply = deliver(notify, "ready")
print(reply)

The receiver passes one value to the callback and returns the callback’s result.
Step 2: Give sorted a Key Function
The key argument to sorted() accepts a function that produces a comparison key for each item. Passing str.casefold makes this sort compare lowercase forms while keeping each original string in the result.
names = ["ada", "Grace", "linus"]
print(sorted(names, key=str.casefold))
print(names)
Output:
['ada', 'Grace', 'linus']
['ada', 'Grace', 'linus']
sorted() calls the key function while building the ordering, then returns a new list. It does not pass the key function’s transformed strings back in place of the original items.
Step 3: Apply map and filter When Iterated
map() and filter() also receive callables, but return iterators. Creating the iterator does not run the full transformation. Iteration requests values and triggers the callback.
def double(value):
return value * 2
values = [2, 4, 6]
pending = map(double, values)
print("map created")
print(list(pending))
positive = filter(lambda value: value > 0, values)
print(list(positive))
| Expression | What happens |
|---|---|
| map(double, values) | Creates an iterator that calls double as values are requested. |
| list(pending) | Consumes the iterator and collects the returned values. |
| filter(predicate, values) | Keeps values for which the predicate returns a truthy result. |
Unlike the sorted() example, the work here is visible at consumption time. If you need the results more than once, store them in a list because an exhausted iterator does not replay its values.
Step 4: Set the Callback Contract in Your Function
A custom receiver should make the callback’s inputs and return value explicit. This one-number contract tells the callback what to accept, and a named function is clearer when its logic needs a separate test or reusable name.
def apply_twice(callback, value):
first_result = callback(value)
return callback(first_result)
result = apply_twice(lambda number: number + 3, 4)
print(result)
Output:
10
The lambda maps 4 to 7 and then 7 to 10, so apply_twice returns 10. That output confirms the receiver feeds each result into the next call.
Verify the Callback Result and Keep State Explicit
A closure is a function that retains access to a name from its enclosing scope. It can carry a configured value without adding another receiver argument.
def make_multiplier(factor):
def multiply(value):
return value * factor
return multiply
double = make_multiplier(2)
print(double(6))
Returning multiply passes the function itself. Returning multiply(value) calls it before the caller receives it, so the retained factor is closure state rather than a property of every callback.
- If Python reports that a value is not callable, check whether you passed a result instead of a function name.
- If the callback raises an argument-count error, compare the receiver’s call with the callback’s parameters.
- A GUI event system such as Tkinter invokes a registered callback when its event occurs. The callback’s arguments depend on that API.
- Calling an async function produces an awaitable. A synchronous receiver does not automatically await it, so use an async-aware API when the callback must be awaited.
Make the Receiver’s Contract Explicit
When you design a callback parameter, document the arguments it receives and whether the receiver uses its return value. That contract tells the next function author whether to pass a function, a closure with captured state, or a callable object.
def apply_twice(callback, value):
return callback(callback(value))
print(apply_twice(lambda number: number + 3, 4))
Callback Function Questions
Is a callback the same as a closure?
No. A callback is a role a callable plays when a receiver accepts and invokes it. A closure retains access to a name in its enclosing scope, and a closure can also be used as a callback.
Why pass a function without parentheses?
Passing a function name gives the receiver the callable so it can choose when and how to invoke it. Parentheses call the function immediately and pass its return value instead.
Can an async function be a callback in Python?
Yes, when the receiving API knows how to handle the awaitable returned by calling it. A synchronous receiver will not automatically await that result.
Does map call its callback immediately?
map returns an iterator. The callback is called as the iterator is consumed, for example by list().


