Learning Goals
3 minBy the end of this lesson you can:
- Write
lambda x: expressionfor tiny one-liners. - Use lambdas as the
key=argument tosorted,min,max. - Recognise when a lambda is appropriate — and when a named
defis clearer. - Avoid the "lambda assignment" anti-pattern that PEP 8 frowns on.
Warm-Up · A Function in One Line
5 minThese two are equivalent:
# Named function def double(x): return x * 2 # Lambda — same thing, anonymous double = lambda x: x * 2 print(double(5)) # 10
The lambda version has three parts: the keyword lambda, parameters, a colon, and one expression. The expression is automatically the return value — no explicit return.
Lambdas are tiny throwaway functions. Anywhere a function expects a function — sorted(key=), max(key=), filter, map — a lambda saves a line.
New Concept · The Shape
14 minThe syntax
lambda PARAMETERS: EXPRESSION
| |
└ same as └ what would normally be after 'return'
def's
parametersThe expression is the return value. No statements allowed — no if-blocks, no loops, no print (well, print works because it's an expression, but it returns None, so the lambda becomes useless).
Two-argument lambdas
add = lambda a, b: a + b print(add(3, 4)) # 7 multiply = lambda a, b: a * b print(multiply(3, 4)) # 12
Parameters separated by commas. Default values work too: lambda x, n=2: x ** n.
The conditional expression trick
You can't use if as a statement, but the conditional expression works:
abs_val = lambda x: x if x >= 0 else -x print(abs_val(-7)) # 7 label = lambda score: "pass" if score >= 50 else "fail" print(label(75)) # pass
Same shape: VALUE_IF_TRUE if CONDITION else VALUE_IF_FALSE. Compact but cramped — if it gets longer than one line, switch to a real def.
The most common use · key=
You've seen this all over Level 2 and 3. sorted, min, max take a key argument — a function that extracts the value to sort/compare by.
words = ["python", "is", "fun", "indeed"] # Sort by length print(sorted(words, key=lambda w: len(w))) # → ['is', 'fun', 'python', 'indeed'] # Find the longest word print(max(words, key=lambda w: len(w))) # 'indeed' # Sort dicts by a key students = [ {"name": "Aisyah", "score": 92}, {"name": "Wei Jie", "score": 87}, ] print(sorted(students, key=lambda s: s["score"], reverse=True))
The lambda is the "by what" — by length, by score, etc. Use the lambda when the key would be a tiny one-liner; use a named function when it's complicated.
Lambdas as values
Lambdas are values. You can store them in lists or dicts:
ops = { "+": lambda a, b: a + b, "-": lambda a, b: a - b, "*": lambda a, b: a * b, "/": lambda a, b: a / b, } print(ops["+"](3, 4)) # 7 print(ops["*"](3, 4)) # 12 # A simple calculator op = input("op: ") a = float(input("a: ")) b = float(input("b: ")) print(ops[op](a, b))
A dict-of-lambdas is the "dispatch table" pattern — much cleaner than a long if-elif chain.
The anti-pattern · assigning a lambda
PEP 8 (Python's style guide) explicitly says: don't do this.
# ❌ Don't square = lambda x: x ** 2 # ✅ Do def square(x): return x ** 2
If you're going to give the function a name, just use def — same syntax, same length, plus you get a proper function name in tracebacks. Lambdas are for anonymous functions you pass directly to something.
Lambda vs def · when to choose which
Use lambda when… Use def when… - 1-line body - more than 1 line - used once, inline - reused - passed as a key= argument - has docstring / type hints - pure value computation - has side effects (print, write)
Worked Example · The Sort Showcase
12 minSave as sort_showcase.py:
# sort_showcase.py — lambdas for sorting students = [ {"name": "Aisyah", "score": 92, "year": 13}, {"name": "Wei Jie", "score": 87, "year": 12}, {"name": "Priya", "score": 95, "year": 13}, {"name": "Iman", "score": 60, "year": 12}, {"name": "Aizat", "score": 81, "year": 13}, ] # 1 — by score, low to high print("By score (asc):") for s in sorted(students, key=lambda s: s["score"]): print(f" {s['name']:<10} {s['score']}") # 2 — by score descending print("\nBy score (desc):") for s in sorted(students, key=lambda s: s["score"], reverse=True): print(f" {s['name']:<10} {s['score']}") # 3 — by name length, ties broken alphabetically print("\nBy name length then alpha:") for s in sorted(students, key=lambda s: (len(s["name"]), s["name"])): print(f" {s['name']:<10}") # 4 — by year ASC then score DESC inside each year print("\nGrouped by year, score desc within:") for s in sorted(students, key=lambda s: (s["year"], -s["score"])): print(f" Year {s['year']}: {s['name']:<10} {s['score']}") # 5 — max top = max(students, key=lambda s: s["score"]) print(f"\nTop scorer: {top['name']} ({top['score']})") # 6 — bottom 3 (slice the sorted list) bottom_3 = sorted(students, key=lambda s: s["score"])[:3] print("\nBottom 3:") for s in bottom_3: print(f" {s['name']:<10} {s['score']}")
Output
By score (asc): Iman 60 Aizat 81 Wei Jie 87 Aisyah 92 Priya 95 By score (desc): Priya 95 Aisyah 92 Wei Jie 87 Aizat 81 Iman 60 By name length then alpha: Iman Aizat Priya Aisyah Wei Jie Grouped by year, score desc within: Year 12: Wei Jie 87 Year 12: Iman 60 Year 13: Priya 95 Year 13: Aisyah 92 Year 13: Aizat 81 Top scorer: Priya (95) Bottom 3: Iman 60 Aizat 81 Wei Jie 87
Read the diff
Six different sorting needs. Each line is one lambda. The two-key sort uses a tuple — (s["year"], -s["score"]) — primary year asc, secondary score desc (negative trick for descending in a tuple sort). The name-length sort has a tie-breaker by name. Each lambda is short enough that adding a named helper would be more code than it saves.
Try It Yourself
13 minSort ["banana", "apple", "cherry", "date"] by the last letter of each word.
Hint
fruits = ["banana", "apple", "cherry", "date"] print(sorted(fruits, key=lambda w: w[-1])) # → ['banana', 'apple', 'date', 'cherry'] -- a, e, e, y
Same shape as PY-L1-24 string indexing, plus a lambda. w[-1] is the last character.
Build a dispatch table for + - * /. Then run a loop that asks for two numbers and an operator until the user types q.
Hint
ops = { "+": lambda a, b: a + b, "-": lambda a, b: a - b, "*": lambda a, b: a * b, "/": lambda a, b: a / b if b != 0 else "ERR", } while True: op = input("op (q to quit): ") if op == "q": break if op not in ops: print("Unknown op"); continue a = float(input("a: ")); b = float(input("b: ")) print(ops[op](a, b))
Cleaner than four if-elif branches. Adding %, ** takes one new dict entry each.
Given cities = [("KL", 1.8M), ("Penang", 700k), ("Ipoh", 750k)], find the city closest in population to a given target.
Hint
cities = [("KL", 1_800_000), ("Penang", 700_000), ("Ipoh", 750_000)] target = 1_000_000 # Sort by distance from target closest = min(cities, key=lambda c: abs(c[1] - target)) print(closest) # ('KL', 1800000) — closest to 1M
abs(c[1] - target) is the distance. min picks the smallest distance. Famous pattern in clustering and search algorithms.
Mini-Challenge · Custom Sort Composer
8 minBuild composer.py. Given a list of dicts and a string describing how to sort (e.g. "score desc, name asc"), build the correct lambda dynamically and return the sorted list.
Supports compound keys (comma-separated) and asc/desc per key.
Show one possible solution
# composer.py — custom multi-key sorter def build_sort_key(spec): """Build a key function from 'field1 dir1, field2 dir2'.""" parts = [] for clause in spec.split(","): bits = clause.strip().split() field = bits[0] descending = (len(bits) > 1 and bits[1].lower() == "desc") parts.append((field, descending)) def keyfunc(item): result = [] for field, descending in parts: v = item[field] # Negate for descending numerics; for strings, negate by tuple of -ord if descending: if isinstance(v, (int, float)): v = -v else: # We'll use a wrapper that flips comparison; simpler: tag tuple v = tuple(-ord(c) for c in v) result.append(v) return tuple(result) return keyfunc students = [ {"name": "Aisyah", "score": 92}, {"name": "Wei Jie", "score": 87}, {"name": "Priya", "score": 92}, {"name": "Iman", "score": 60}, ] # Sort by score desc, then name asc ranked = sorted(students, key=build_sort_key("score desc, name asc")) for s in ranked: print(s)
Non-negotiables: a function that returns a lambda (or equivalent) configured by a spec string. Real sort UIs do exactly this — "Sort by: score↓ → name↑" comes from a spec. The string-descending hack is ugly; a cleaner approach uses functools.cmp_to_key — try that next week.
Recap
3 minA lambda is a one-expression anonymous function. Use it as a key= argument to sorted, min, max; as a value in a dispatch dict; or anywhere a tiny throwaway function fits. Conditional expressions (x if cond else y) work inside lambdas; statements don't. If you're tempted to assign a lambda to a variable — just use def instead. Lambdas earn their keep precisely because they're anonymous.
Vocabulary Card
- lambda
- An anonymous one-expression function.
- key= argument
- The
sorted/min/maxparameter that specifies what to compare by. - dispatch table
- A dict mapping names (or operators) to functions. Replaces long if-elif chains.
- conditional expression
VALUE_IF_TRUE if CONDITION else VALUE_IF_FALSE— the only "if" allowed inside a lambda.
Homework
4 minBuild music_sort.py. Given the list:
songs = [ {"title": "Levitating", "artist": "Dua Lipa", "plays": 124, "duration": 203}, {"title": "New Rules", "artist": "Dua Lipa", "plays": 89, "duration": 209}, {"title": "Bad Guy", "artist": "Eilish", "plays": 156, "duration": 194}, {"title": "Cupid", "artist": "FIFTY FIFTY", "plays": 195, "duration": 174}, {"title": "Dynamite", "artist": "BTS", "plays": 210, "duration": 199}, ]
Print:
- Songs sorted by play count (descending) using a lambda.
- The song with the longest title (use
max+ lambda). - Songs sorted alphabetically by artist, then by play count within an artist.
- The shortest song overall (use
min+ lambda).
Sample · music_sort.py
# 1 — by plays desc for s in sorted(songs, key=lambda s: s["plays"], reverse=True): print(f" {s['plays']:>4} {s['title']:<14} by {s['artist']}") # 2 — longest title longest = max(songs, key=lambda s: len(s["title"])) print(f"\nLongest title: {longest['title']}") # 3 — by artist, then plays print("\nBy artist, then plays:") for s in sorted(songs, key=lambda s: (s["artist"], -s["plays"])): print(f" {s['artist']:<12}{s['title']:<14}{s['plays']}") # 4 — shortest song shortest = min(songs, key=lambda s: s["duration"]) print(f"\nShortest: {shortest['title']} ({shortest['duration']}s)")
Non-negotiables: every sort uses a lambda key=, the multi-key sort uses a tuple with negation for the descending part. (s["artist"], -s["plays"]) sorts artist alphabetical, then plays high-to-low within each artist.