python
functions
function-arguments
higher-order-functions
duplicate-post

Passing functions with arguments to another function in Python?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In Python, you pass a function to another function by passing the callable itself, not by calling it immediately. The moment arguments are involved, the real question becomes whether you want to pass the callback and the arguments separately, or pre-bind some arguments and pass a new callable that already knows them.

Pass the Callable, Not the Result

The first distinction is the one that causes most confusion. These two forms are not the same:

python
1def greet(name):
2    return f"hello {name}"
3
4
5def run(callback, name):
6    return callback(name)
7
8
9print(run(greet, "Ada"))

This works because greet is passed as a function object.

If you wrote run(greet("Ada"), "Ada"), Python would call greet first and pass its return value into run. That means you are no longer passing a function at all.

Forward Arbitrary Arguments With *args and **kwargs

If the wrapper should work with many different callback signatures, use argument forwarding.

python
1def run(callback, *args, **kwargs):
2    return callback(*args, **kwargs)
3
4
5def power(base, exp=2):
6    return base ** exp
7
8
9print(run(power, 3, exp=3))

This is the standard higher-order function pattern when your wrapper is generic and should not know the exact parameter list of the callback.

Pre-Bind Arguments With functools.partial

If the callback should always be invoked with some fixed arguments, partial is often cleaner than inventing a custom wrapper each time.

python
1from functools import partial
2
3
4def multiply(x, factor):
5    return x * factor
6
7
8double = partial(multiply, factor=2)
9triple = partial(multiply, factor=3)
10
11print(double(10))
12print(triple(10))

This creates new callables that remember some of the original arguments. It is especially useful in callback registration, scheduling, and GUI code.

Use lambda for Small Adapters

For a tiny one-off transformation, a lambda is perfectly fine.

python
1def transform(items, fn):
2    return [fn(item) for item in items]
3
4
5numbers = [1, 2, 3]
6print(transform(numbers, lambda x: x + 10))

The limit is readability. Once the logic becomes nontrivial, a named function is usually better for testing and debugging.

Methods Are Callables Too

Bound methods carry object state automatically, so you can pass them like any other callable.

python
1class Logger:
2    def __init__(self, prefix):
3        self.prefix = prefix
4
5    def log(self, message):
6        print(f"{self.prefix}: {message}")
7
8
9def emit(callback, payload):
10    callback(f"received {payload}")
11
12
13logger = Logger("worker")
14emit(logger.log, "task-42")

This is often cleaner than passing both an object and a free function separately.

Document the Expected Signature

When one function accepts another function, the expected callback signature should be made clear. Type hints help a lot here.

python
1from typing import Callable
2
3
4def apply_twice(fn: Callable[[int], int], value: int) -> int:
5    return fn(fn(value))
6
7
8print(apply_twice(lambda x: x + 1, 5))

This does not replace tests, but it makes the contract much more explicit for readers and static analysis tools.

Async Callables Need Separate Handling

If the callback is asynchronous, treat that as a separate API shape instead of trying to blur sync and async behavior together.

python
1import asyncio
2
3
4async def run_async(callback, *args, **kwargs):
5    return await callback(*args, **kwargs)
6
7
8async def fetch_value(x):
9    return x + 1
10
11
12print(asyncio.run(run_async(fetch_value, 41)))

Being explicit about sync versus async callbacks avoids a lot of confusion later.

Common Pitfalls

The biggest mistake is calling the function immediately instead of passing the callable reference. Another is forgetting to forward keyword arguments when writing a generic wrapper.

Developers also often overuse lambda for logic that should be named and tested. A small adapter is fine. Hidden business logic inside nested lambdas is not.

Finally, do not make one API accept both sync and async callbacks unless you really need that complexity. Separate interfaces are usually easier to understand and maintain.

Summary

  • Pass the function object itself when another function should call it later.
  • Use *args and **kwargs when forwarding arbitrary arguments.
  • Use functools.partial to pre-bind arguments into a new callable.
  • Use lambda only for small local adapters.
  • Keep callback signatures explicit, especially in larger or async-heavy codebases.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.