- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Mastering Python Functions
Every Python script starts innocently: ten lines, one goal, total control. Then comes a quick fix. Then another. Then a function called do_stuff() that slowly becomes the entire application. Six months later, you open the file and whisper: “Who wrote this?” … And surprise—it was you. Functions are how you fight back. In this hands-on Code Lab, you will learn how to design, create, and apply Python functions using parameters, return values, and modular programming techniques to solve real problems efficiently. You will start with simple def blocks, then level up to building flexible, reusable functions that transform copy-paste chaos into clean, maintainable code. Along the way, you will break larger problems into smaller pieces, pass data cleanly between functions (input → process → output), and build solutions that are easier to read, test, and debug—even at 2 AM. By the end of the lab, you will think in functions: split the problem, name the pieces, wire them together, and ship code that still makes sense tomorrow. Cleaner Python. Less panic. A slightly happier future version of you.
Lab Info
Table of Contents
-
Challenge
Introduction
Background
Welcome to the Store Checkout Toolkit Lab.
You've just joined the engineering team of a small online store. The store sells notebooks, mugs, stickers, and the occasional Mystery Box. Business is growing fast … the codebase is not.
Every time Marketing launches a promotion, discount logic gets copy-pasted across checkout, receipts, and sales reports. It works… until it doesn't.
Last week, for the same product, three different systems reported three different prices:
| Service | Price shown | |---------|-------------| | Checkout | EUR 9.00 | | Receipt | EUR 10.00 | | Sales report | EUR 8.50 |
Same product. Three results. One very unhappy customer and one very confused finance team.
Your mission: refactor this fragile system into a clean, reusable Python toolkit using functions, so every service agrees on the price and Marketing can change promotions without breaking half the store.
No pressure 😅.
--- ## What you'll build
This lab takes you from duplicated discount math to a modular checkout pipeline.
You will progressively move from copy-pasted formulas to structured, reusable code:
- Extract one discount function used by checkout, receipts, and sales reports.
- Parameterize the discount logic for regular, VIP, and clearance customers.
- Compose small helper functions into a full order total (subtotal → discount → tax).
- Return values from functions so each result can be reused, tested, and passed to the next step.
Unlike a single homework script, real checkout systems are touched by many files, many teams, and many promotions. A small duplication today becomes three conflicting prices tomorrow.
--- ## The three stages of the lab
| Step | Theme | What you build | |------|-------|----------------| | 1 | Remove duplication | Extract
apply_store_discountand wire checkout, receipt, and sales report to call it | | 2 | Flexible promotions | Upgrade toapply_discount(price, percent_off)for regular, VIP, and clearance tiers | | 3 | Checkout pipeline | Implementline_total,add_tax, andorder_total— discount before tax |--- ## Three beginner mistakes
Store code rarely breaks in only one way. This lab targets three common beginner habits:
| Mistake | What goes wrong | What you'll do instead | |---------|-----------------|------------------------| | Copy-paste logic | Same formula in checkout, receipt, and report — prices drift apart | One function, many callers | | Hardcoded rules |
0.15baked into every file — every new promo needs surgery |percent_offas a parameter | | Printing inside logic |print()in a discount function — hard to test and reuse |returnthe value; print only in demos |Understanding why these habits hurt maintainability is as important as getting the math right.
--- ## More details
Why duplicated formulas cause bugs
Writing
price * (1 - 0.15)in three places might feel faster, but it creates technical debt.Every copy is a future bug waiting to happen:
- The Marketing team changes the rate, but someone forgets to update the receipt script.
- The Finance team fixes the sales report, but checkout still uses the old formula.
- A new engineer copies the pattern again, and now there are four sources of truth.
What functions give you
Functions solve a different problem than making code shorter. They give you:
- One place to change business rules: update the discount once, not in five scripts.
- Parameters: one function handles 10%, 20%, or 30% off without copy-paste variants.
- Return values: compute a result, pass it to the next step, and test it in isolation.
- Composition: build
order_totalfromline_total,apply_discount, andadd_tax.
How the checkout pipeline works
Checkout here is not one long script. It is a pipeline: define each step once, call it in the right order, and let orchestration glue the story together.
Subtotal → discount → tax ## Core elements In this lab, you will work with: **The bug story** Three services should show the same price, but they do not because the 15% discount formula was duplicated instead of shared. **`apply_store_discount`** Step 1's single source of truth for the standard store discount. One function, three call sites. **Customer tiers** Regular (10% off), VIP (20% off), and clearance (30% off). Step 2 replaces a hardcoded rate with a parameter. **The cart format** Orders are lists of line items: ```python items = [{"qty": 2, "price": 12.50}, {"qty": 1, "price": 8.00}]The checkout pipeline
The checkout flow follows this order: Subtotal → discount → tax. Each step is its own function;order_totalorchestrates without repeating math.Processing order (important)
Apply the discount before calculating tax. In this lab, tax is calculated on the discounted amount.Pre-written orchestration
Therun_step1,run_step2,run_step3, and receipt formatting code are already provided. You will focus on the core function logic.
Learning objectives
By the end of this lab, you should be able to:
- Define reusable Python functions with
def - Replace duplicated logic with a single function called from multiple places
- Use parameters to make one function flexible for different inputs
- Return values from functions and reuse them in later computations
- Break a checkout flow into small functions with single responsibilities
- Compose functions into a pipeline (
order_total→line_total+apply_discount+add_tax) - Explain why discount must be applied before tax in this pipeline
--- ## Lab structure
You will work primarily inside the
src/directory.Each step contains guided
TODOsections. Orchestrators, receipt demos, andrun_step*output are already provided so you can focus on the function concepts.| File | Your focus | |------|------------| |
src/step1_remove_duplication.py| Extract discount logic; connect checkout, receipt, and report | |src/step2_flexible_discounts.py| Parameterizedapply_discount| |src/step3_checkout_pipeline.py| Line totals, tax, and fullorder_total|--- ## You're ready to begin
Open
src/step1_remove_duplication.py.Your first task: extract the duplicated 15% discount into
apply_store_discountso checkout, receipts, and sales reports all return the same price for the same product.Let's begin!
If you get stuck at any point, full reference implementations are provided in the
solution/folder in your file tree. -
Challenge
Step 1 — Remove the discount duplication
The situation
The store applies a 15% discount in three places: checkout, printed receipt, and sales report.
There is no shared function. Each team copy-pasted discount math straight into its own script:
# checkout.py original_price = 10.00 sale_price = original_price * (1 - 0.10) # never updated from old promo # receipt.py original_price = 10.00 sale_price = original_price # discount step missing # report.py original_price = 10.00 sale_price = original_price * (1 - 0.15) # correct rate, still duplicatedThe Marketing team sent one email. Three teams pasted different versions. Nobody noticed until a customer complained.
See the bug first
Open
src/step1_remove_duplication.pyand run it as-is:python3 src/step1_remove_duplication.pyThe
BEFOREblock simulates those three scripts. They still use copy-pasted code and have no reusable function:=== BEFORE refactor — copy-pasted code, no shared function === Original price: EUR 10.00 Checkout: EUR 9.00 Receipt: EUR 10.00 Sales report: EUR 8.50 Same product, three prices — three scripts, three copy-pastes.Scroll to the
PRE-WRITTEN — Before refactorsection. You'll see:run_checkout_scriptrun_receipt_scriptrun_report_script- Each function has its own inline
sale_price = ...line.
Do not edit that block. It shows how things worked (badly) before you introduce functions.
Below that, the
TODOsection is empty. That's where you build the refactor.
What you'll build
By the end of this step:
apply_store_discount(price)— the first shared discount function (replacing all that copy-paste)checkout_price,receipt_price, andreport_sale_price— thin wrappers that call it- Running the file again shows the
AFTERblock where all three services returnEUR 8.50:
=== AFTER refactor — one function, three callers === ... All three services agree — refactor complete.
Your tasks
You will work through 4 functions in order in
src/step1_remove_duplication.py. ## Step completeWhen all four tasks pass Validate, run the experiment script using the following command:
python3 src/step1_remove_duplication.pyBEFOREblock still shows the old copy-pasted mess (unchanged)AFTERblock shows EUR 8.50 from all three refactored functions
You can move now to Step 2:
src/step2_flexible_discounts.py -
Challenge
Step 2 — Create flexible promotions
The situation
Your Step 1 refactor worked. For about five minutes.
Marketing now wants different discounts by customer type:
| Customer type | Discount | |---------------|----------| | Regular | 10% off | | VIP | 20% off | | Clearance | 30% off |
apply_store_discountonly knows 15%. The quick fix — the one every tired team reaches for — is to copy the function again:def apply_regular_discount(price): return round(price * (1 - 0.10), 2) def apply_vip_discount(price): return round(price * (1 - 0.20), 2) def apply_clearance_discount(price): return round(price * (1 - 0.30), 2)Three promos, three functions. Sound familiar? You just solved duplication in Step 1. Don't recreate it with a different number.
See the problem first
Open
src/step2_flexible_discounts.pyand run it as-is:python3 src/step2_flexible_discounts.pyThe
BEFOREblock shows the three-function approach still working:=== BEFORE refactor — one hardcoded function per promo === Base price: EUR 50.00 regular EUR 45.00 vip EUR 40.00 clearance EUR 35.00 Three promos, three functions — what happens when Marketing adds a fourth?Scroll to
PRE-WRITTEN — Before refactor. You'll see:apply_regular_discountapply_vip_discountapply_clearance_discount
Each function uses the same pattern with a different hardcoded rate. Do not edit that block.
Below it,
apply_discountis empty. That's your upgrade.
What you'll build
By the end of this step:
- One function,
apply_discount(price, percent_off), that supports any discount percentage - A returned discounted price (rounded to cents)
- No
printinside the function
The
run_step2()function reusesapply_discountfor all three tiers on aEUR 50.00base price:=== AFTER refactor — one function, any percent_off === Base price: EUR 50.00 regular EUR 45.00 vip EUR 40.00 clearance EUR 35.00 One function handles every tier — add a fourth promo without a new function.Note: Step 1 used
apply_store_discount(fixed 15%). Step 2 introducesapply_discount(flexible). That's intentional because you're evolving the design, not starting over.
Your task
You will complete 1 function in
src/step2_flexible_discounts.py, then Validate (seetests text).--- ## Step complete
When Task 5 passes Validate, run the experiment script using the following command:
python3 src/step2_flexible_discounts.pyBEFOREblock still shows three separate hardcoded functions (unchanged)AFTERblock printsregular/vip/clearanceprices via oneapply_discount
You can move now to Step 3:
src/step3_checkout_pipeline.py -
Challenge
Step 3 — Build the checkout pipeline
The situation
Marketing wants a full checkout system for every order:
- Multiple items in the cart
- Customer-specific discount (from Step 2)
- Tax on the final amount
- One total everyone agrees on
The old approach? One long script with every calculation inline:
# old checkout.py — everything in one block subtotal = 0.0 for item in items: subtotal = subtotal + item["qty"] * item["price"] after_discount = subtotal * (1 - percent_off / 100) final = after_discount * (1 + tax_rate)It produces a number. But you can't reuse
line_totalon the receipt, test the discount logic alone, or easily spot a workflow bug such as applying tax before the discount.
See the problem first
Open
src/step3_checkout_pipeline.pyand run it as-is:python3 src/step3_checkout_pipeline.pyThe
BEFOREblock runsrun_checkout_inline, where all math happens in one function:=== BEFORE refactor — one script, all math inline === subtotal = sum(qty * price) after_discount = subtotal * (1 - percent_off / 100) final = after_discount * (1 + tax_rate) Cart total: EUR 32.08 It works once — but try reusing 'discount logic' on the receipt and report.Scroll to
PRE-WRITTEN — Before refactor. Do not edit that block. It shows the monolithic version.Below it, three empty functions wait for you:
line_total,add_tax,order_total.
Input format
Every cart is a list of line items:
items = [{"qty": 2, "price": 12.50}, {"qty": 1, "price": 8.00}]qty— how many unitsprice— unit price in euros
Processing rules
Each function owns one step.
order_totalcalls them in this order:| Order | Step | Function | What it does | |-------|------|----------|----------------| | 1 | Subtotal |
line_total(qty, price)per item, then sum |2×12.50 + 1×8.00 = 33.00| | 2 | Discount |apply_discount(subtotal, percent_off)|33.00with 10% off →29.70| | 3 | Tax |add_tax(discounted_amount, tax_rate)|29.70with 8% tax →32.08|Important:
Make sure to apply the discount before calculating tax. In this lab, tax is calculated on the discounted amount, not the original subtotal.
Worked example (10% off, 8% tax):
Line 1: 2 × EUR 12.50 = EUR 25.00 Line 2: 1 × EUR 8.00 = EUR 8.00 ------------------------- Subtotal: EUR 33.00 Discount (10%): EUR 29.70 Tax (8% of 29.70): EUR 2.38 TOTAL: EUR 32.08
What you'll build
By the end of this step:
line_total(quantity, unit_price)— one line item's costadd_tax(amount, tax_rate): calculates tax on an amount where0.08means 8%.order_total(items, percent_off, tax_rate): orchestrates the full pipelineapply_discountis imported from Step 2 — not reimplementedrun_step3(): prints a full receipt viaformat_receipt(pre-written)
=== AFTER refactor — small functions, clear pipeline === --- Store Receipt --- 2 x EUR 12.50 = EUR 25.00 1 x EUR 8.00 = EUR 8.00 Subtotal: EUR 33.00 Discount: 10% off -> EUR 29.70 Tax: EUR 2.38 TOTAL: EUR 32.08
Your tasks
You will work through 3 functions in order in
src/step3_checkout_pipeline.py, validating after each (seetests text).--- ## Validate
After each task:
pytest tests/test_step3_pipeline.py -vLMS (one task at a time):
python scripts/validate.py tests/test_step3_pipeline.py -k "test_line_total_matches_reference" python scripts/validate.py tests/test_step3_pipeline.py -k "test_add_tax_matches_reference" python scripts/validate.py tests/test_step3_pipeline.py -k "test_order_total_matches_reference"Run the full script when done:
python src/step3_checkout_pipeline.pyThe
AFTERblock should print the receipt withTOTAL: EUR 32.08.
Lab complete
When all three Step 3 tasks pass Validate, run the experiment script using the following command:
python3 src/step3_checkout_pipeline.pyBEFOREblock still shows inlinerun_checkout_inline(unchanged)AFTERblock prints a full receipt withTOTAL: EUR 32.08order_totalcallsline_total,apply_discount, andadd_tax— no inline pipeline math- Discount is applied before tax
The lab is finished. When Marketing launches the next promotion, learners update one function — not five scripts.
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.