Built-in Functions
Python ships with a toolbox of built-in functions — always there, no import
needed. For our Bill Splitter, they turn whole loops into single words: total a
table with sum, find the priciest plate with max, number a receipt with
enumerate. This lesson builds up to bill_stats(prices), the function that
gives the manager a one-glance read on any table.
Every box is live — edit and ▶ Run.
Part 1 — Measuring the table: len and sum
Two questions come up on every bill: how many things and how much total.
len counts the items; sum adds them all. In lesson 5 you totalled a list by
hand with a for loop — sum(prices) does exactly that in one word, faster and
clearer.
❓ Cross-question — "Why
len(x)and notx.length/x.size()?" Python favours free functions over methods for universal operations:len(x),sum(x),min(x),max(x),sorted(x)all work on any sequence (list, tuple, str, set, dict) — one consistent call instead of a different method per type.
Try it: add another plate to
pricesand re-run. Both numbers update — no loop, no running total, no off-by-one.
Note:
sumstarts from0by default, sosum([])is0(not an error).len([])is0too. These two never crash on an empty list — remember that, it matters in Part 9.
Part 2 — The extremes: min, max, and sorted
max finds the priciest plate, min the cheapest. sorted returns a new
list in order and leaves your original untouched — handy when you want a ranked
menu but still need the plates in their original serving order.
That "classic mix-up" is worth meeting head-on before it costs you an hour:
prices = [24.0, 8.0, 12.5]
cheapest_first = prices.sort()
print(cheapest_first)Watch out:
sorted(prices)gives back a sorted copy. The last line provespricesitself is unchanged. (Thelist.sort()method sorts in place and returnsNone— a classic mix-up.) Passreverse=Truetosortedfor a most-expensive-first ranking.
❓ Cross-question — "Is
sorted(x)like[...x].sort()?" Yes —sorted(x)returns a new list (copy-then-sort), whilex.sort()mutates in place and returnsNone— so never writey = x.sort(). Python's sort is stable, same as JavaScript's.
Part 3 — Money math: abs and round
abs strips the sign — perfect for "how far off" a split is from even. round
trims a float to a set number of decimals, which is how you keep dollars looking
like dollars instead of 19.8760000001.
Watch out — banker's rounding.
round(2.5)is2, not3, andround(3.5)is4. Python rounds a tie to the nearest even number. It's deliberate (it reduces bias over many roundings), but it surprises everyone once. For real invoicing use thedecimalmodule; for this playground,round(x, 2)on totals is fine.
Part 4 — enumerate: index and item together
Need the position and the value while looping? enumerate hands you both, so
you never have to babysit a manual counter. It yields (index, item) pairs, and
start=1 makes a human-friendly numbered receipt.
In a real loop you unpack the pair directly (this is the idiom you'll reach for):
for i, name in enumerate(items, start=1):
print(f"{i}. {name}")Try it: change
start=1tostart=0(the default) and watch the numbers shift. Menus read better starting at 1; array logic usually wants 0.
Part 5 — zip: two lists in lockstep
Menu names live in one list, their prices in another. zip walks them together,
pairing items[0] with prices[0], and so on — no index juggling.
zip stops at the shortest list, so mismatched lengths silently drop the
extras — a subtle source of "where did my last item go?" bugs:
Note:
zipis a common partner forsum. To total item-by-item you can loopfor name, price in zip(items, prices):and add up as you go.
Part 6 — any and all: quick sanity checks
all is True only when every condition holds; any is True when at
least one does. Paired with a generator expression, they validate a whole list
in one readable line — "are all prices positive?", "is any plate over $20?".
Watch out: on an empty list
all([])isTrue(nothing broke the rule) andany([])isFalse(nothing matched). These "vacuous truth" cases are logically correct but easy to forget when your input might be empty.
Part 7 — range: counting on demand
range produces a sequence of numbers without building a giant list in memory —
ideal for "do this N times" or generating table numbers. range(stop) starts at
0; range(start, stop) and range(start, stop, step) give you full control. The
stop value is excluded.
Note:
rangeis lazy — it doesn't materialise the numbers until you ask. That last line sums 1..100 without ever building the list, which is whysum(range(...))scales to huge counts effortlessly. Wrap it inlist(...)only when you actually want to see the values, as above.
Part 8 — map and filter: transform and select
map applies a function to every item; filter keeps only the items that pass a
test. Both are lazy — they return an iterator, so you wrap them in list(...)
to see the results. Here we add an 18% tip to each plate, then keep only the
higher-priced dishes.
Try it: most Pythonistas write these as comprehensions instead —
[round(p * 1.18, 2) for p in prices]and[p for p in prices if p >= 20]read more naturally. Knowmap/filterwhen you see them, but reach for comprehensions in your own code.
Part 9 — Guarding empty sequences
Here's the trap that shapes today's task: sum([]) and len([]) are happy
(they return 0), but max([]) and min([]) raise ValueError. If a table
could have zero items, you must check before calling them.
Without a guard, max([]) blows up:
max([]) # ValueError: max() iterable argument is emptyTwo safe patterns: pass default=0 (shown above), or guard with if not prices:
and return early. The graded bill_stats uses the guard — it's explicit and it
lets you return a full all-zeros dict in one place.
Watch out: an empty list is falsy, so
if not prices:reads as "if there are no prices". This one check is the difference between a clean $0.00 summary and a crash mid-service.
Part 10 — Putting it together: a stats dict
A dictionary maps keys to values (you'll go deep on these in Section 2). It's the perfect return type when a function computes several named results at once — exactly what the manager wants: total, count, average, highest, lowest.
Note: every built-in you met above is doing one line of work here. Now add the Part 9 empty-list guard on top and you have
bill_stats— the function you're about to write.
Part 11 — Sorting & selecting with a key
You've met sorted, min, max, map. Their real power is the key=
argument — a function that says what to compare by. This is the single most
useful builtin idiom for real data.
sorted(key=…) — sort by anything
Coming from Java/JS:
key=is a selector (return the field to sort on), not a two-arg comparator. It boils JS'sarr.sort((a,b) => a.price - b.price)down tokey=lambda d: d.price— and each key is computed once.
❓ Cross-question — "How do I do a multi-field comparator like in TS?" Return a tuple from
key— Python compares tuples left to right, sokey=lambda x: (a, b)sorts byathen breaks ties byb. For descending on a number, negate it (-x.score). No chained.thenComparing()needed.
min/max also take key=
Multi-key sort — sort by A, break ties by B
Return a tuple from the key; Python compares tuples element by element:
operator.itemgetter(1) does the same as lambda d: d[1], faster and clearer:
reversed() and map over two lists
Real-world — a leaderboard: sort players by score (high→low), then name:
🎯 Your turn
Write bill_stats(prices) — it returns a summary dict with keys total, count, average, highest, lowest:
bill_stats([10, 20, 30])→{"total": 60, "count": 3, "average": 20.0, "highest": 30, "lowest": 10}bill_stats([])→{"total": 0, "count": 0, "average": 0.0, "highest": 0, "lowest": 0}
Hint — guard the empty list first, then use sum, len, max, min and round.
Then press ▶ Run, tap the Live App try chips to call it with different inputs, and hit ✓ Check. Green = this piece of the app is built. ✅
