You've already USED dozens of functions — print(), len(), type(), round() — without writing one. Each is a named machine: data goes in, work happens, a result comes out. Today you build your own machines, and your code stops being a script and starts being a toolbox.
What you'll learn
What
A function is a named, reusable block of code defined with def. You call it by name with parentheses, optionally passing inputs, and it hands control back when it finishes — usually with a result via return (which the next lesson covers in depth).
Why
Copy-pasted logic drifts: fix a bug in one copy and the other three keep it. Functions give logic ONE home — one place to fix, test, and name. Every serious codebase, from pandas to your future pipelines, is functions calling functions.
Where it's used
Every data pipeline stage (load, clean, transform, report), every test, every library API you'll call — read_csv, fit, predict are all just functions someone defined.
Where this runs in production
Audio-feature extraction is a chain of named functions — normalize_loudness, compute_tempo — each testable alone, composed into the pipeline that scores every uploaded track.
Quant researchers must ship strategies as small pure functions — pricing, signal, risk — because reviewers reject thousand-line scripts nobody can audit.
One clean_product_name() function is imported by dozens of internal jobs — a single fix to its logic corrected months of subtle catalog duplicates everywhere at once.
def greet(name):
print(f'Hello, {name}!')
greet('Ada')
greet('Grace')One definition, two calls. Each call jumps into the body with name bound to the argument, runs it, and jumps back. The parentheses in a call are not optional — greet alone is the function itself (a value!), greet('Ada') runs it.
Writing a recipe card doesn't cook anything — it records the steps once. Cooking happens when someone follows the card, and the same card can be followed a hundred times with different ingredients. def writes the card; the call cooks. And like a good recipe card, the name on top ('Weeknight Curry') should say what you get.
The most reliable design rule in programming: a function should do ONE thing, stated by its name. load_csv() loads. remove_duplicates() removes. If the honest name would be load_and_clean_and_plot(), that's three functions being held hostage. Small single-purpose functions are testable, reusable, and — crucially — readable as a table of contents for your logic.
def celsius_to_fahrenheit(c):
"""Convert a Celsius temperature to Fahrenheit."""
return c * 9 / 5 + 32
print(celsius_to_fahrenheit(100)) # 212.0The triple-quoted string right under the def is a docstring — the function's built-in documentation, shown by help() and your editor's hover. One clear sentence is enough. return sends the result back to the caller; the full story of parameters and return is next lesson.
greet('Ada') calls the function. greet without parentheses is a REFERENCE to it — a value you can store or pass around (that's how higher-order functions will work later this module). The classic bug: writing result = get_total instead of result = get_total(), then wondering why result is '<function get_total at 0x...>' instead of a number.
Execution jumps into the body and back. Click each stage to follow the control flow.
A reusable banner printer for a report script.
A function with no parameters — the parentheses are empty but still required. Defining it prints nothing.
Your task
Build a tiny discount toolkit. Define apply_discount(price, pct) returning the discounted price rounded to 2 decimals, and format_price(amount) returning a '$xx.xx' string. Then use both to print the final price of a $80.00 item at 15% off.
$68.00
Write your solution in the editor on the right, then hit Run.
What does defining a function with def actually execute?
After `def get_rate(): return 0.08`, what does `x = get_rate` (no parentheses) make x?
Which function name best follows the one-job rule and naming conventions?
Why, and what's the fix?
Define count_longer_than(words, n) that returns how many strings in words have more than n characters. Then print count_longer_than(['data', 'ai', 'python', 'ml'], 3). Expected output: 2
A script calls report() on line 5, and defines `def report(): ...` on line 20. What happens when the script runs?
Build a two-function pipeline: to_floats(strings) converts a list of numeric strings to floats; total(values) returns their sum. Compose them to print the total of ['12.5', '7.25', '30.0']. Expected output: 49.75
Why do functions matter for code quality? Name the concrete benefits.
Four concrete wins. Reuse: logic written once is called everywhere, so a fix lands everywhere at once instead of drifting across copies. Testability: a small function with inputs and a return value can be verified in isolation with three lines of test data — a 400-line script cannot. Readability: well-named functions turn a script into a table of contents; readers grasp the flow without reading every body. Abstraction: callers depend on the NAME and contract, not the implementation, so you can rewrite the inside (faster algorithm, different library) without touching call sites. The underlying principle is single responsibility — one function, one job — because everything above follows from it.
What's the difference between definition time and call time, and what bug class does the distinction explain?
def executes when Python reads it: it builds the function object and binds the name — but never runs the body. The body runs at call time, freshly, for every call. This explains two bug classes. First, NameErrors from calling above the definition in a script — at that line, the name doesn't exist yet. Second, and more important: broken bodies that hide. A function whose body would crash — a typo'd variable, a bad conversion — defines without complaint and detonates only on first call, possibly weeks later in production on the one input path nobody exercised. That's precisely why untested code paths are treated as unshipped code on mature teams.
print() versus return — explain the difference and when each is appropriate.
return hands a value back to the caller, making the call expression stand for that value — it can be stored, compared, summed, or passed onward; that's what makes functions composable. print() writes text to the screen for a human and returns None; the 'result' is unrecoverable by code. So: computation functions should return, and the caller — usually at the program's edge — decides what to print. A function that prints instead of returning poisons composition: subtotal + tax(80) becomes number + None and crashes. The habit to state in interviews: return for programs, print for people, and keep printing at the outermost layer.
Common Mistakes to Avoid
1) Printing inside a function when the caller needs the value — return it instead. 2) Forgetting parentheses: get_total is the function, get_total() is its result. 3) Calling a function above its def in a script — NameError. 4) One mega-function doing five jobs — if the name needs 'and', split it. 5) Vague names (process, handle, do_it) that force readers into the body. 6) Assuming a clean definition means working code — bodies only fail at call time, so call it (test it) before trusting it.
Ask the AI Tutor
Try these prompts in the AI Tutor panel: • 'Show me a 20-line script and help me split it into functions.' • 'Quiz me: which of these five defs runs code at definition time?' • 'Critique these function names and suggest better ones.' • 'Show the print-instead-of-return bug and its stack trace.' • 'Interview mode: ask me why small functions beat big ones and grade my answer.'
Glossary
Function — a named, reusable block of code run by calling it. def — the keyword that defines a function. Call — executing a function with name(arguments). Parameter — the placeholder name in the definition. Argument — the actual value passed in a call. Body — the indented block that runs at call time. Docstring — the triple-quoted description right under the def. return — statement sending a value back to the caller. Definition time — when def executes (binds the name). Call time — when the body actually runs. Composition — feeding one function's return value into another.
Recommended Resources
• Docs: 'Defining Functions' in the official Python tutorial. • Read: PEP 257 (docstring conventions) — two minutes, lifelong habit. • Practice: take your longest script from earlier lessons and refactor it into three named functions with a three-line main section. • Next in DSM: parameters deserve their own lesson — Parameters, Arguments & Return Values covers multiple inputs, multiple returns, None, and the contracts that make functions trustworthy.
Recap
✓ def names a block; calling name() runs it — definition time records, call time executes. ✓ Parameters receive arguments; each call is a fresh, independent run. ✓ return hands values back for further computation; print is only for humans. ✓ One function, one job, verb-first snake_case name, one-line docstring. ✓ Functions calling functions turn scripts into readable, testable pipelines. ✓ A function reference without () is a value — useful later, a classic bug today. Next up: Parameters, Arguments & Return Values. You can define and call — now master what flows in and out: multiple parameters, multiple returns, None, and early returns.
Run your code to see the output here.