All 57 lessons

Lesson 17 of 57 · Functions

Python Functions: Write Code Once, Use It Everywhere

By Skillancy Editorial Team

Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).

So far you have written code that runs top to bottom once. Real programs repeat the same logic again and again: format a rupee amount, clean a city name, compute a delivery fee. Copying and pasting that code is a trap, because the day the rule changes you must find and fix every copy. A function gives a piece of logic a name, so you write it once and call it as many times as you like.

You have already used many functions: print, len, sum, sorted. In this lesson you will write your own. Our running example is a real problem for Indian data work: formatting money the Indian way, where 1234567 is written ₹12,34,567, with the first group of three digits followed by groups of two. Python's built-in , formatter only knows the Western style, so we will build the right tool ourselves.

The shape of a function

def function_name(parameter1, parameter2):
    """One line saying what it does."""
    result = parameter1 + parameter2
    return result

def starts the definition, the name follows the same rules as variable names, and the parameters in brackets are the inputs. The indented body runs only when the function is called. return hands a value back to whoever called it.

Worked examples

Example 1: Your first function

Question: Write a function that adds GST at 5% to a price.

def add_gst(price):
    return price * 1.05

print(add_gst(100))
print(add_gst(250))

bill = add_gst(480)
print(round(bill, 2))

Output

105.0
262.5
504.0

How it works: price is a parameter, a name for whatever value the caller passes in. return sends the computed value back, so add_gst(100) can be printed or stored, just like len(...) gives back a number. Defining the function does nothing visible; the work happens on each call.

Reading the result: A ₹100 item becomes ₹105.00 with tax, and ₹480 becomes ₹504.00. If the tax rate ever changes you edit one line.

Example 2: print is not return

Question: What goes wrong if a function prints instead of returning?

def total_printing(a, b):
    print(a + b)

def total_returning(a, b):
    return a + b

x = total_printing(2, 3)
y = total_returning(2, 3)

print("x is", x)
print("y is", y)
print(y * 10)

Output

5
x is None
y is 5
50

How it works: The first function displays a number but gives back nothing, so x holds None. The second returns the value, so it can be used in further calculations. A function without a return statement returns None automatically.

Reading the result: x is None while y is 5, and only y can be multiplied. Rule of thumb: functions should return results and let the caller decide whether to print them.

Example 3: Several parameters

Question: Compute a delivery fee from the distance and the order value.

def delivery_fee(distance_km, order_value):
    if order_value >= 499:
        return 0
    return 15 + 5 * distance_km

print(delivery_fee(2, 300))
print(delivery_fee(4, 300))
print(delivery_fee(4, 600))

Output

25
35
0

How it works: Arguments are matched to parameters by position: the first value is distance_km, the second is order_value. A function can have several return statements, and the first one reached ends the call immediately.

Reading the result: A 2 km order of ₹300 pays ₹25, a 4 km one pays ₹35, and any order at or above ₹499 is free (0). Early return statements keep functions flat and readable.

Example 4: Docstrings

Question: How do you document a function so others know how to use it?

def discount_price(price, pct):
    """Return price after applying a percentage discount.

    price: original price in rupees
    pct: discount percentage, for example 10 for 10 percent
    """
    return price * (1 - pct / 100)

print(discount_price(200, 10))
print(discount_price.__doc__.splitlines()[0])
help(discount_price)

Output

180.0
Return price after applying a percentage discount.
Help on function discount_price in module __main__:

discount_price(price, pct)
    Return price after applying a percentage discount.

    price: original price in rupees
    pct: discount percentage, for example 10 for 10 percent

How it works: A string placed as the first statement in the body is the docstring. Python stores it, and help() or an editor tooltip shows it. Good docstrings state what the function returns and what each parameter means.

Reading the result: A 10 percent discount on ₹200 gives 180.0. The first docstring line, "Return price after applying a percentage discount.", is what tools usually display.

Example 5: Returning several values

Question: Return both the total and the average of a list.

def summarize(values):
    total = sum(values)
    average = total / len(values)
    return total, average

t, a = summarize([68, 45, 96, 62])
print(t, a)

result = summarize([10, 20])
print(result, type(result))

Output

271 67.75
(30, 15.0) <class 'tuple'>

How it works: return total, average actually returns a single tuple, which we met in the tuples lesson. The caller can unpack it into two names, or keep the whole tuple.

Reading the result: The four prices total 271 with an average of 67.75. The type of result is tuple, which confirms a tuple came back.

Example 6: Scope

Question: Can a function see and change outside variables?

rate = 0.05

def with_tax(price):
    tax = price * rate
    return price + tax

print(with_tax(100))

try:
    print(tax)
except NameError as e:
    print("NameError:", e)

def broken():
    counter = counter + 1

try:
    broken()
except UnboundLocalError as e:
    print("UnboundLocalError:", e)

Output

105.0
NameError: name 'tax' is not defined
UnboundLocalError: cannot access local variable 'counter' where it is not associated with a value

How it works: A function can read a global variable like rate. Variables created inside a function, like tax, are local and disappear when the call ends. Assigning to a name inside a function creates a local variable, which is why broken fails: Python treats counter as local, but it has no value yet. The global keyword exists but passing values in and returning them is almost always cleaner.

Reading the result: The tax-inclusive price is 105.00, tax does not exist outside, and the broken function raises an error because of the local assignment. Prefer parameters over globals.

Example 7: Building the Indian number formatter, part one

Question: Python gives 1,234,567. We want 12,34,567. Start by understanding the grouping rule.

print(f"{1234567:,}")

def indian_commas(n):
    s = str(int(n))
    if len(s) <= 3:
        return s
    last3 = s[-3:]
    rest = s[:-3]
    groups = []
    while len(rest) > 2:
        groups.insert(0, rest[-2:])
        rest = rest[:-2]
    if rest:
        groups.insert(0, rest)
    return ",".join(groups + [last3])

for n in [999, 1000, 54321, 1234567, 123456789]:
    print(indian_commas(n))

Output

1,234,567
999
1,000
54,321
12,34,567
12,34,56,789

How it works: The rule is: the last three digits form one group, and everything before them is cut into groups of two from the right. The function converts the number to text, takes the last three characters, then peels two digits off the remaining front part repeatedly with a while loop, inserting each group at the front of the list. Finally it joins the groups with commas.

Reading the result: Compare the Western 1,234,567 with the Indian 12,34,567. The function handles short numbers as well, such as 54,321.

Example 8: The formatter, part two: rupees, lakhs and crores

Question: Wrap it in a function that also supports negatives, decimals and a ₹ sign, then show a compact "lakh/crore" form.

def inr(amount, decimals=0):
    """Format a number as Indian rupees, e.g. 1234567 -> ₹12,34,567."""
    sign = "-" if amount < 0 else ""
    amount = abs(amount)
    whole = indian_commas(round(amount, decimals) // 1)
    if decimals:
        frac = f"{round(amount, decimals):.{decimals}f}".split(".")[1]
        return f"{sign}₹{whole}.{frac}"
    return f"{sign}₹{whole}"

def short_inr(amount):
    """Return a compact form such as 12.35 L or 1.2 Cr."""
    if amount >= 1e7:
        return f"{amount / 1e7:.2f} Cr"
    if amount >= 1e5:
        return f"{amount / 1e5:.2f} L"
    return inr(amount)

print(inr(1234567))
print(inr(1234567.891, 2))
print(inr(-45000))
print(short_inr(12345678))
print(short_inr(250000))
print(short_inr(9500))

Output

₹12,34,567
₹12,34,567.89
-₹45,000
1.23 Cr
2.50 L
₹9,500

How it works: The function reuses indian_commas for the whole-number part and handles the decimal part separately. The decimals=0 parameter has a default value, so callers may omit it. short_inr uses 1e5 (one lakh) and 1e7 (one crore) as thresholds.

Reading the result: We now get ₹12,34,567, ₹12,34,567.89 and -₹45,000. The compact form shows 1.23 crore as 1.23 Cr and a quarter million as 2.50 L. You will use these helpers in later lessons to make reports look right for an Indian audience.

Example 9: Functions with real data

Question: Apply the formatter to FreshBasket's actual revenue.

import pandas as pd

items = pd.read_csv("order_items.csv")
orders = pd.read_csv("orders.csv")
print(list(items.columns))

delivered_ids = set(orders.loc[orders["order_status"] == "Delivered", "order_id"])
d = items[items["order_id"].isin(delivered_ids)]
revenue = float((d["quantity"] * d["unit_price"]).sum())

print(inr(revenue))
print(short_inr(revenue))

Output

['order_id', 'product_id', 'quantity', 'unit_price']
₹35,33,834
35.34 L

How it works: We keep only line items from delivered orders, multiply quantity by unit price for each line, and sum the result. Then our own functions format the number. Notice that the formatting logic lives in one place and is reused whenever we need it.

Reading the result: Delivered revenue is ₹35,33,834, which is 35.34 L. In a report this reads far better than a raw float like 3533834.0.

Example 10: Calling functions from loops and comprehensions

Question: Apply one function to many values.

amounts = [999, 54321, 1234567, 98765432]

print([inr(a) for a in amounts])

for a in amounts:
    print(f"{a:>12,} -> {inr(a):>14}")

print(list(map(short_inr, amounts)))

Output

['₹999', '₹54,321', '₹12,34,567', '₹9,87,65,432']
         999 ->           ₹999
      54,321 ->        ₹54,321
   1,234,567 ->     ₹12,34,567
  98,765,432 ->   ₹9,87,65,432
['₹999', '₹54,321', '12.35 L', '9.88 Cr']

How it works: A function is just a value you can call anywhere. Comprehensions and loops call it for each item, and map applies a function to every element of a list and returns an iterator, which list collects.

Reading the result: The last amount, 98,765,432 in Western style, becomes ₹9,87,65,432 and 9.88 Cr.

Common mistakes

1. Forgetting to call the function. add_gst is the function object; add_gst(100) runs it.

def add_gst(price):
    return price * 1.05
print(add_gst)

Output

<function add_gst at 0x7fcbf4c4b380>

2. Using print instead of return and then wondering why the result is None.

3. Calling with the wrong number of arguments.

def add_gst(price):
    return price * 1.05
add_gst(100, 5)

Output

Traceback (most recent call last):
  File "example.py", line 3, in <module>
    add_gst(100, 5)
    ~~~~~~~^^^^^^^^
TypeError: add_gst() takes 1 positional argument but 2 were given

4. Defining a function after using it. Python reads top to bottom, so the def must run before the call.

5. Modifying globals from inside a function instead of returning a new value.

Practice questions

Practice 1 (Easy): Convert minutes

Write to_hours(minutes) that returns minutes divided by 60 rounded to 1 decimal. Print the result for 90 and for 135.

Show solution
def to_hours(minutes):
    return round(minutes / 60, 1)

print(to_hours(90), to_hours(135))

Output

1.5 2.2

The function returns a value, so we can print as many as we like: 1.5 and 2.2.

Practice 2 (Medium): Grade a delivery

Write grade(minutes) returning "A" for 20 minutes or less, "B" for 21 to 35, and "C" otherwise. Apply it to [12, 25, 40, 20, 36].

Show solution
def grade(minutes):
    if minutes <= 20:
        return "A"
    if minutes <= 35:
        return "B"
    return "C"

print([grade(m) for m in [12, 25, 40, 20, 36]])

Output

['A', 'B', 'C', 'A', 'C']

Early returns replace elif chains. The grades are ['A', 'B', 'C', 'A', 'C'].

Practice 3 (Hard): A reusable stats function

Write describe(values) that returns a tuple (minimum, maximum, mean) and use it on the delivery minutes of all delivered orders. Format the mean to one decimal.

Show solution
def describe(values):
    return min(values), max(values), sum(values) / len(values)

delivered = orders[orders["order_status"] == "Delivered"]["delivery_minutes"].tolist()
lo, hi, mean = describe(delivered)
print(lo, hi, f"{mean:.1f}")

Output

8.0 54.0 23.7

Returning a tuple lets one call give several answers. Deliveries took between 8 and 54 minutes, averaging 23.7.

Key takeaways

  • def name(params): defines a function, and the body runs only when you call it.
  • return gives a value back; without it the function returns None.
  • Prefer returning results over printing them, so the caller decides what to do.
  • Docstrings document what the function does and are shown by help().
  • Variables made inside a function are local; pass values in through parameters.
  • Return a tuple to hand back several values, and unpack it at the call site.
  • You now have inr() and short_inr() for Indian-style money formatting.

Frequently asked questions

What is the difference between a parameter and an argument?

A parameter is the name in the definition (price), and an argument is the actual value you pass in the call (100). People often use them interchangeably.

Can a function call another function?

Yes. Our inr function calls indian_commas, and short_inr calls inr. Building small functions that call each other is how large programs are organised.

Why does my function return None?

Most likely it has no return statement, or the return is inside an if that did not run. Every path through the function should return what you expect.

Should functions be short?

Aim for one clear job per function. If you struggle to name it without using "and", it probably does two things and should be split.

Can I define a function inside a function?

Yes, but it is rarely needed at this stage. You will meet it again with closures and decorators.

Is there a limit to the number of parameters?

Technically no, but more than four or five is a sign to group values into a dictionary or an object.

Newsletter

Keep learning Python

One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.

Ready to practise? Try the Python interview questions