Skip to content

Python · Functional Programming

Python functools Explained — lru_cache, partial, reduce, wraps, and cached_property

4 min read Updated 2026-06-19 Share:

Practice functools interview questions

Python functools, explained

functools is the standard library's toolbox for higher-order functions — utilities that take or return functions. A handful of its tools show up constantly in real code and interviews: lru_cache, partial, reduce, wraps, and cached_property. Here's what each does and when to reach for it.

lru_cache — memoisation for free

Decorating a function with lru_cache stores results keyed by arguments, so repeated calls with the same inputs return instantly. It turns exponential recursion into linear time.

from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

fib(100)            # fast — each subproblem computed once
fib.cache_info()    # CacheInfo(hits=..., misses=101, maxsize=None, currsize=101)

maxsize bounds the cache (least-recently-used items evicted). Arguments must be hashable, and the cached function should be pure — caching a function with side effects or that depends on external state will give stale results. Python 3.9+ adds @cache as shorthand for lru_cache(maxsize=None).

partial — pre-fill arguments

partial builds a new callable with some arguments already supplied. It's a clean alternative to lambdas for "the same function but with this argument fixed."

from functools import partial

def power(base, exp):
    return base ** exp

square = partial(power, exp=2)
cube = partial(power, exp=3)

square(5)    # 25
cube(5)      # 125

Common use: adapting a function to a callback API that passes fewer arguments, or building specialised versions like int_base2 = partial(int, base=2).

reduce — fold a sequence to one value

reduce repeatedly applies a two-argument function across an iterable, accumulating a single result. It's the general form behind sum, max, and friends.

from functools import reduce

product = reduce(lambda acc, x: acc * x, [1, 2, 3, 4], 1)   # 24

The third argument is the initial value (and the result for an empty iterable). Prefer a plain loop or built-in when one exists — reduce is most justified for genuine folds like running a chain of compositions.

wraps — keep the wrapped function's identity

When you write a decorator, the wrapper replaces the original, losing its __name__ and docstring. functools.wraps copies that metadata back.

from functools import wraps

def log(func):
    @wraps(func)                # without this, greet.__name__ == 'wrapper'
    def wrapper(*args, **kwargs):
        print(f"calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log
def greet(): "say hi"
greet.__name__   # 'greet'

Always apply @wraps in your decorators — it keeps introspection, debuggers, and docs working.

cached_property — compute once per instance

cached_property turns an expensive method into an attribute computed on first access and then stored on the instance, so later accesses are free.

from functools import cached_property

class Dataset:
    def __init__(self, rows):
        self.rows = rows

    @cached_property
    def stats(self):
        print("computing...")
        return {"count": len(self.rows), "total": sum(self.rows)}

d = Dataset([1, 2, 3])
d.stats     # "computing..." then the dict
d.stats     # cached — no recompute

Because the result is stored in the instance __dict__, it lives as long as the instance. Use it for derived values that are costly and don't change.

reduce vs comprehensions, and other tools

functools also offers singledispatch (function overloading by argument type), total_ordering (fill in comparison methods from __eq__ and one of __lt__ etc.), and cmp_to_key (adapt old-style comparison functions for sorted). These are niche but handy when they fit.

Recap

functools packages the most useful higher-order helpers: lru_cache/cache memoise pure, hashable-argument functions; partial pre-fills arguments to make specialised callables; reduce folds an iterable to a single value; wraps preserves a wrapped function's name and docstring inside decorators; and cached_property computes an expensive attribute once per instance. Round it out with singledispatch and total_ordering when you need type-based dispatch or auto-generated comparisons.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel