ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 08

  1. Blog

functools.partial vs lambda: Call Lab

Aditya Challa·30 September 2026·4 min read

Summary
On this page
  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — hot call (p50)
  5. Bind / create (p50)
  6. map / genexp
  7. Reading it
  8. Partial vs default-arg lambda
  9. Pitfalls
  10. When to pick what
  11. Reproduce
  12. Closing

Intro — what this post promises

Binding a fixed argument: is functools.partial cheaper to call than a lambda or a nested def factory? This lab times bind-once hot calls, callable creation, and map on Linux localhost.

Related links:

  • attrgetter vs getattr localhost lab
  • itemgetter vs lambda sort localhost lab
  • statistics vs manual mean localhost lab
  • bisect vs linear lookup localhost lab
  • itertools chain vs flatten localhost lab
  • Counter vs dict tally localhost lab
  • lru_cache hit vs miss localhost lab
  • perf_counter vs time localhost lab

Lab honesty (1 Oct 2026 IST): Python 3.13.5. N=500,000 calls/arm after binding once (except recreate arm). Affiliates: 0. Direct add(fixed, x) remains the ceiling.

Verdict up front: partial ~15.6M/s vs lambda ~13.8M (~1.13×); direct ~18.8M (~1.20× partial). Recreating a lambda every call drops to ~8.4M (partial ~1.9× faster). Prefer partial for reusable callables; never rebuild wrappers in the hot loop.


Arms

ArmPattern
directadd(FIXED, i) in loop
partialf = partial(add, FIXED); f(i)
lambdaf = lambda x, f=FIXED: add(f, x)
nested deffactory returns inner
recreate lambdanew lambda every iteration
map / genexphigher-order consume

Lab topology

N = 500000 hot calls; bind creation = 50000
metric: p50 ops/s

Script: lab-evidence/70-functools-partial-vs-lambda/results/run_lab.py.


Lead table — hot call (p50)

Armops/sns/op
direct18,814,01653.2
partial15,628,03864.0
nested def14,534,38468.8
lambda13,811,10972.4
lambda recreate each8,375,880119.4

Bind / create (p50)

Armcreates/sns/create
lambda expr16,983,12558.9
partial11,372,11487.9
nested factory10,222,42697.8

Lambda is cheaper to create (~1.49× partial) but partial wins on repeated calls.


map / genexp

Armops/s
genexp direct21,051,176
map(partial)20,294,146
map(nested)18,270,198
map(lambda)17,592,197

map(partial) ~1.15× map(lambda). Two-arg bind: partial still ~1.07× lambda.


Reading it

  • partial is a thin C wrapper — slightly faster calls than Python lambda/nested here.
  • Bind once — recreate-each is the real foot-gun (~1.9× tax vs partial).
  • Direct still wins if you do not need a callable object.
  • Nested def ≈ lambda on call; use it when you want a name / docstring / multi-statement body.

Partial vs default-arg lambda

lambda x, f=FIXED: add(f, x) binds FIXED at definition time — same idea as partial’s frozen args. The gap in this lab is mostly call convention (C partial object vs Python function), not binding semantics. If a teammate reads partial(add, 7) more clearly than a default-arg lambda, ship partial; if the expression is a one-liner in a local scope, lambda is fine. The non-negotiable rule is create the wrapper once.


Pitfalls

  1. lambda inside a loop that captures a changing loop variable — bind with default (x=i) or use partial carefully.
  2. Micro-optimizing partial vs lambda — tens of ns; readability first.
  3. Ignoring creation cost when you build millions of short-lived wrappers — lambda create was faster here.
  4. Assuming partial is always free — still a call vs inlining direct args.

When to pick what

NeedPrefer
Reusable callable for map/key=functools.partial
Tiny one-off expressionlambda
Multi-line / named closurenested def
Hottest path, no callable neededdirect call

Reproduce

python3 lab-evidence/70-functools-partial-vs-lambda/results/run_lab.py

Evidence: /workspace/lab-evidence/70-functools-partial-vs-lambda/results/.


Closing

Partial edges lambda on hot calls; bind once. On this box partial hit ~15.6M/s vs lambda ~13.8M (~1.13×) and crushed per-iteration lambda rebuild by ~1.9×. Use partial for stable wrappers; keep direct calls when a function object is optional.

functools.partiallambdanested defcall overheadpythonlocalhost labsrepartial

Lab evidence

What I found running this

Lab 1 Oct 2026 IST. Python 3.13.5; N=500000. call partial 15.6M vs lambda 13.8M (~1.13x); direct 18.8M (~1.20x partial); recreate-lambda each call ~1.9x slower than partial. Affiliates: 0. Evidence: lab-evidence/70-functools-partial-vs-lambda/.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Related links

  • Plate 17

    platform vs os.uname Inventory: Localhost Lab

    Hands-on platform.platform vs os.uname host inventory lab: real ops/s plus cache notes, measured on Linux localhost today in this hands-on lab for SREs.

    1 Oct 2026

  • Plate 50

    signal vs threading.Event Wakeup: Localhost Lab

    Hands-on signal SIGUSR1 vs threading.Event wakeup lab: real p50 latency in microseconds, measured on Linux localhost today in this hands-on lab for SREs.

    1 Oct 2026

  • Plate 76

    cmath vs math.hypot Magnitudes: Localhost Lab

    Hands-on cmath vs math.hypot magnitude ops lab: real ops/s for abs, polar, and phase, measured on Linux localhost today in this hands-on lab for SREs.

    1 Oct 2026

On this page

  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — hot call (p50)
  5. Bind / create (p50)
  6. map / genexp
  7. Reading it
  8. Partial vs default-arg lambda
  9. Pitfalls
  10. When to pick what
  11. Reproduce
  12. Closing
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove