Python
lambda functions
list comprehensions
duplicate
programming tips

Lambda function in list comprehensions

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Using lambdas inside list comprehensions is legal in Python, but it often surprises developers due to late binding of loop variables. The result is many lambdas returning the same final value instead of distinct values. Understanding closure binding timing is the key to writing correct and readable code.

Core Sections

Why the classic bug happens

Consider this common pattern:

python
funcs = [lambda: i for i in range(4)]
print([f() for f in funcs])

Expected by many people is [0, 1, 2, 3]. Actual output is [3, 3, 3, 3] because each lambda closes over the same variable i, which ends as 3 after loop completes.

This is called late binding in closures.

Correct capture with default argument

Capture loop value at lambda creation time by using default parameter.

python
funcs = [lambda i=i: i for i in range(4)]
print([f() for f in funcs])  # [0, 1, 2, 3]

i=i binds current value into function defaults, creating distinct captured values.

Alternative patterns that improve readability

Sometimes lambdas in comprehensions reduce clarity. Named helper functions or functools.partial can be easier to maintain.

python
1from functools import partial
2
3def identity(x):
4    return x
5
6funcs = [partial(identity, i) for i in range(4)]
7print([f() for f in funcs])

Readable code is usually more valuable than compact expression tricks in production modules.

Immediate invocation versus deferred invocation

If you only need computed values now, do not build deferred lambdas. Compute directly in comprehension.

python
squares = [i * i for i in range(5)]
print(squares)

Deferred callables should be used only when later invocation is required, such as callback registration or task scheduling.

Lambdas in async or callback-heavy code

Late binding issues become more painful in async contexts where callbacks run much later.

python
1callbacks = []
2for i in range(3):
3    callbacks.append(lambda i=i: print("task", i))
4
5for cb in callbacks:
6    cb()

Always bind loop values explicitly when callback execution is delayed.

Debugging checklist

When output seems duplicated:

  1. inspect whether lambda closes over a loop variable
  2. print function defaults to confirm capture
  3. convert lambda to named function temporarily for clarity
  4. write a quick assertion test on expected callback outputs

Small tests prevent closure bugs from resurfacing during refactors.

Performance notes

In most app code, correctness and readability matter more than micro-optimizing lambda creation. If performance is critical, benchmark concrete alternatives using your real workload rather than relying on assumptions.

Team code-style guidance

A practical style rule is:

  • allow lambda in comprehensions only when behavior is obvious
  • require explicit capture pattern when using loop variables
  • prefer named functions when logic grows beyond one expression

This reduces onboarding friction and code review ambiguity. In test suites, include one explicit assertion that generated callbacks produce unique outputs for representative indices. That single test catches most accidental regressions when refactoring comprehension logic. This is especially valuable in async callback registration paths. Keep this check close to callback factory code.

When code reviews surface deferred lambda usage, request one explicit capture example in tests so future refactors do not reintroduce late-binding bugs.

Common Pitfalls

  • Expecting loop variables in lambdas to be captured by value automatically.
  • Using deferred lambdas when immediate computation is sufficient.
  • Mixing readability and clever one-liners in callback-heavy code paths.
  • Debugging output values without checking closure binding semantics.
  • Copying examples that work in one scope but fail in asynchronous flow.

Summary

  • Lambda plus list comprehension is powerful but closure behavior can be unintuitive.
  • Late binding causes many lambdas to share the same final loop variable value.
  • Use default argument capture such as i=i for correct per-item binding.
  • Prefer simpler alternatives when deferred callables are unnecessary.
  • Add targeted tests for callback outputs to catch regressions early.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

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

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.