1. Lighthouse

Level 1 · A line-total function with a discount

The request

From Angela Rivera, Head of Customer Support

“Support keeps redoing line totals by hand, and discounts make it worse. Can you write one function they can call for any line, with the discount optional?”

Your goal

Define line_total(quantity, unit_price, discount_pct=0.0) that returns quantity times unit_price times (1 minus discount_pct), rounded to 2 decimals. discount_pct is a fraction (0.15 means 15% off). Set result to the list [line_total(2, 49.99), line_total(3, 24.00, 0.15), line_total(1, 129.00, discount_pct=0.2)].

  • Store a list of 3 in resultnot done yet

Your code

Python

Loading the editor…

    ResultPress Run to see yours

    Mote checks your result

    Press Run. Each row that matches the goal becomes a firefly in your lantern.

    The lesson

    Naming a piece of logic

    A function is defined with def, a name, parameters in brackets, and a colon. The indented body does the work, and return sends a value back to whoever called it:

    def line_total(quantity, unit_price):
        return quantity * unit_price
    
    line_total(2, 49.99)    # 99.98

    Defining a function doesn’t run it. Python just remembers the recipe. It runs when you call it with brackets and actual values, called arguments. Inside the function, the parameters behave like ordinary variables that exist only for that call.

    return versus print

    print shows a value on screen; return hands it back so you can store it or use it in further calculations:

    total = line_total(2, 49.99) + 7.50    # works because line_total returns a number

    A function without a return (or with a bare pass) returns None. That’s the most common function bug for beginners: the function prints the right number, but every value built from it is None, and the next line fails with TypeError: unsupported operand type(s) for +: 'NoneType' and 'float'.

    return also ends the function immediately. Nothing after it in the body runs.

    Default arguments

    Most cart lines have no discount, so it would be tedious to pass 0 every time. Give the parameter a default with = in the definition:

    def line_total(quantity, unit_price, discount_pct=0.0):
        return round(quantity * unit_price * (1 - discount_pct), 2)
    
    line_total(2, 49.99)          # 99.98   discount_pct is 0.0
    line_total(3, 24.00, 0.15)    # 61.2    15% off

    Parameters with defaults must come after the ones without, so def f(a=1, b) is a syntax error.

    Keyword arguments

    You can pass any argument by name instead of position:

    line_total(1, 129.00, discount_pct=0.2)
    line_total(unit_price=129.00, quantity=1, discount_pct=0.2)

    Named arguments make calls self-explanatory. Compare line_total(1, 129, 0.2) with the version above: three months later, the second one still tells you what 0.2 means. pandas relies on this heavily; calls like df.sort_values("unit_price", ascending=False) are everywhere.

    Percentages: fraction or number?

    Decide on one convention and say it in the function. Here discount_pct=0.15 means 15%. If someone passes 15 by mistake, the “discounted” price becomes negative. Real datasets mix both conventions, so always check a few values before using a discount column.

    Docstrings

    A short string right after the def line documents the function, and tools show it as help:

    def line_total(quantity, unit_price, discount_pct=0.0):
        """Return the line total after a fractional discount, rounded to cents."""
        return round(quantity * unit_price * (1 - discount_pct), 2)

    Common mistakes

    • Leaving pass in place or forgetting return, so every call gives None.
    • Writing discount_pct as a percentage (15) instead of a fraction (0.15).
    • Calling the function before defining it. Python reads top to bottom, so the def must run first.