Currying in Python — Core Concepts

What Is Currying?

Currying transforms a function that takes multiple arguments into a sequence of functions that each take a single argument. Named after mathematician Haskell Curry, it’s a core concept in functional programming.

Given a function f(a, b, c), currying produces f(a)(b)(c) — three function calls, each consuming one argument.

Currying vs Partial Application

These terms get confused constantly, but they’re different:

  • Currying converts f(a, b, c) into f(a)(b)(c) — always one argument at a time.
  • Partial application fixes some arguments and returns a function that takes the rest: partial(f, a) gives you g(b, c).

In practice, Python’s toolz.curry blends both: you can provide one argument or several, and it returns a new function waiting for whatever’s left.

How to Curry in Python

Manual Currying with Closures

def multiply(x):
    def inner(y):
        return x * y
    return inner

double = multiply(2)
triple = multiply(3)
double(10)  # 20
triple(10)  # 30

This works for two arguments but gets tedious for functions with more.

Using functools.partial

from functools import partial

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

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

partial is not true currying — it’s partial application — but it solves many of the same problems.

Using toolz.curry

from toolz import curry

@curry
def add(x, y, z):
    return x + y + z

add(1)(2)(3)      # 6 — true currying
add(1, 2)(3)      # 6 — partial application
add(1)(2, 3)      # 6 — mixed
add(1, 2, 3)      # 6 — normal call

The @curry decorator inspects the function’s signature and waits until all required arguments are supplied before executing.

Common Misconception

“Currying is just a fancy way to set defaults.” Defaults are fixed at definition time. Currying creates new specialized functions at runtime based on context. A curried format_price(currency)(amount) can produce format_usd, format_eur, and format_gbp — each a reusable function, not just a value with a default.

When Currying Shines

  • Pipeline stages — Curried functions slot naturally into map(), filter(), and composition chains without writing lambdas.
  • Configuration — Curry a general function with environment-specific settings early, then use the specialized version everywhere.
  • Event handlers — Create handlers with pre-baked context: on_click = handle_event("button_save").
  • Testing — Curry a database function with a mock connection to get a pure function for unit tests.

When to Avoid It

  • Functions with complex keyword arguments or *args/**kwargs don’t curry well.
  • Overusing currying in a codebase where teammates aren’t familiar with it creates confusion.
  • If you’re only fixing one argument once, partial is simpler and more explicit.

One Thing to Remember

Currying turns one function into a chain of specialized functions — give it arguments one at a time and get reusable building blocks at every step.

pythonfunctional-programmingcurrying

See Also

  • Python Function Composition Discover how snapping small Python functions together creates powerful new ones — like building words from letters.
  • Python Functional Pipelines See how chaining small Python functions into a pipeline turns messy data work into a clean assembly line.
  • Python Monads In Python Understand monads through a simple lunchbox analogy — no math degree required, just curiosity.
  • Ci Cd Why big apps can ship updates every day without turning your phone into a glitchy mess — CI/CD is the behind-the-scenes quality gate and delivery truck.
  • Containerization Why does software that works on your computer break on everyone else's? Containers fix that — and they're why Netflix can deploy 100 updates a day without the site going down.