Functions
Level: 6 | Version: v1.0 | Author: Meptrasoft
A function is a reusable block of code designed to perform a specific task. This module uses a Food Delivery Order System as the running example.
def calculate_total():
price = 200
quantity = 2
total = price * quantity
print(total)
calculate_total()
Output:
400
Defining & Calling
A function lets us write logic once and reuse it many times.
Without a function, the same calculation is repeated:
price = 200
quantity = 2
total = price * quantity
print(total)
price = 150
quantity = 3
total = price * quantity
print(total)
Repeated logic creates duplicate code, more maintenance, more bugs, and harder-to-read programs. Using a function:
def calculate_total(price, quantity):
print(return price * quantity)
print(calculate_total(200, 2))
print(calculate_total(150, 3))
print(calculate_total(300, 2))
Output:
400
450
600
Why Functions Matter in Real Projects
Functions are used to calculate order totals, apply discounts, validate input, process payments, send emails, fetch database records, call APIs, generate reports, and update order status. Functions organize these tasks into reusable units.
A function commonly follows this flow:
INPUT → FUNCTION → PROCESS → OUTPUT
def calculate_total(price, quantity):
return price * quantity
print(calculate_total(200, 2))
Output:
400
Defining a Function
Creating a function is called defining it. Python uses the def keyword.
def function_name():
statement
Example:
def show_order():
print("Order Received")
Output:
def → keyword used to define a function
show_order → function name
() → parameters are written here
: → starts the function block
indented → function body
Defining Does Not Execute
def show_order():
print("Order Received")
Output: no output. The function was only defined, not executed.
def → DEFINE / CREATE FUNCTION (not execute)
Calling a Function
Executing a function is called calling it — the function name followed by parentheses.
def show_order():
print("Order Received")
show_order()
Output:
Order Received
How calling works:
def show_order():
print("Order Received")
print("Start")
show_order()
print("End")
Output:
Start
Order Received
End
Program → show_order() → Execute Function Body → Return to Calling Point
Defining vs Calling
| Defining | Calling |
|---|---|
| Creates a function | Executes a function |
Uses def |
Uses function name with () |
| Writes function logic | Runs function logic |
def → DEFINE () → CALL
DEFINE ≠ EXECUTE CALL → EXECUTE
Parameters and Arguments
def calculate_total(price, quantity):
return price * quantity
print(calculate_total(200, 2))
A parameter is a name written in the function definition (price,
quantity). An argument is the actual value passed during a call (200,
2).
Output:
400
| Parameter | Argument |
|---|---|
| Name in function definition | Actual value passed to function |
| Receives data | Sends data |
price |
200 |
quantity |
2 |
PARAMETER → RECEIVES VALUE
ARGUMENT → SENDS VALUE
Inside the call calculate_total(200, 2): price = 200, quantity = 2, so
200 × 2 = 400.
Argument Types
Python supports different ways to pass arguments and define parameters.
| Feature | Purpose |
|---|---|
| Positional Arguments | Match values by position |
| Keyword Arguments | Match values by parameter name |
| Default Parameter Values | Use a predefined value when omitted |
*args |
Accept extra positional arguments |
**kwargs |
Accept extra keyword arguments |
Positional Arguments
Positional arguments are matched based on their position.
def place_order(food, quantity):
print("Food:", food)
print("Quantity:", quantity)
place_order("Burger", 2)
Output:
Food: Burger
Quantity: 2
Python matches the 1st argument to the 1st parameter, and so on. If order is wrong, values are still matched by position:
def place_order(food, quantity):
place_order(2, "Burger") # food ← 2, quantity ← "Burger"
Output:
POSITIONAL ARGUMENT → POSITION MATTERS
Keyword Arguments
Keyword arguments pass values using parameter names, so order can change.
def place_order(food, quantity):
print("Food:", food)
print("Quantity:", quantity)
place_order(food="Burger", quantity=2)
place_order(quantity=2, food="Burger")
Both print the same result.
| Positional | Keyword |
|---|---|
| Matched by position | Matched by parameter name |
| Position matters | Keyword order does not matter |
| Shorter | More explicit |
Output:
Food: Burger
Quantity: 2
Food: Burger
Quantity: 2
Default Arguments
A parameter can have a predefined default value, used when the caller omits it.
def calculate_bill(price, delivery_fee=40):
return price + delivery_fee
print(calculate_bill(200)) # 240 (delivery_fee defaults to 40)
print(calculate_bill(200, 60)) # 260 (default overridden)
Default values are useful for delivery fees, tax rates, retry counts, user roles, page sizes, and timeouts.
For beginners, use simple immutable default values such as numbers, strings,
True, False, or None. Avoid mutable default values such as [] or {};
they can keep changes between function calls.
Parameter Order Rule
Parameters without default values must come before parameters with defaults.
Correct:
def calculate_bill(price, delivery_fee=40):
pass
Wrong (causes SyntaxError):
def calculate_bill(delivery_fee=40, price):
pass
Output:
REQUIRED PARAMETER → FIRST
DEFAULT PARAMETER → AFTER
Variable-Length Positional Arguments — *args
*args allows a function to accept a variable number of positional arguments,
collected into a tuple.
def calculate_order(*prices):
print(prices)
calculate_order(200, 150, 300) # (200, 150, 300)
Sum multiple prices:
def calculate_order(*prices):
return sum(prices)
print(calculate_order(200))
print(calculate_order(200, 150))
print(calculate_order(200, 150, 300))
The same function accepts different numbers of arguments — 1, 5, 10, or 100
prices. The name args is only a convention; the * creates the behavior.
Output:
200
350
650
Variable-Length Keyword Arguments — **kwargs
**kwargs allows a function to accept a variable number of keyword arguments,
collected into a dictionary.
def create_order(**details):
print(details)
create_order(customer="Arun", food="Burger", quantity=2)
Output:
{'customer': 'Arun', 'food': 'Burger', 'quantity': 2}
Access values and iterate:
def create_order(**details):
for key, value in details.items():
print(key, ":", value)
create_order(
customer="Arun",
food="Burger",
quantity=2,
payment="UPI",
coupon="SAVE50"
)
**kwargs is useful for user profiles, API parameters, configuration settings,
database filters, product details, and order metadata.
Output:
customer : Arun
food : Burger
quantity : 2
payment : UPI
coupon : SAVE50
*args vs **kwargs
*args |
**kwargs |
|---|---|
| Extra positional arguments | Extra keyword arguments |
| Collected into a tuple | Collected into a dictionary |
| Values | Key-value pairs |
function(10, 20) |
function(name="Arun") |
* |
** |
*args → VALUES → TUPLE
**kwargs → KEY=VALUE → DICTIONARY
Scope & Return
Return Values
The return statement sends a result from a function back to the caller.
def calculate_total(price, quantity):
return price * quantity
total = calculate_total(200, 2)
print(total) # 400
The returned value can be stored, reused, compared, or passed to another function:
def calculate_total(price, quantity):
return price * quantity
total = calculate_total(200, 2)
final_bill = total + 40
print(final_bill)
Output:
440
print() vs return
print() only displays a value; return sends the value back to the caller.
print() |
return |
|---|---|
| Displays a value | Gives a value back |
| Used to show output | Used in program logic |
| Mainly for display or debugging | Result can be stored |
| Does not return the displayed value | Sends a result to the caller |
print → SHOW VALUE
return → GIVE VALUE BACK
return Stops the Function
def calculate_total():
return 400
print("Completed") # unreachable
Code after an unconditional return in the same execution path is unreachable.
Output:
return → FUNCTION CALL ENDS
Function Without Explicit return
def show_order():
print("Order Received")
result = show_order()
print(result)
Output:
Order Received
None
With no explicit return, Python returns None.
NO EXPLICIT return → None
Variable Scope
Scope defines where a name can be accessed or resolved. The two basic scopes are local and global.
A variable created inside a function is local:
def calculate_total():
total = 400
print(total)
Accessing total outside raises NameError, because it was created inside the
function (local scope).
A variable created at module level is global:
delivery_fee = 40
def show_fee():
print(delivery_fee)
show_fee() # 40
| Local | Global |
|---|---|
| Created inside a function | Created at module level |
| Belongs to local scope | Belongs to global/module scope |
| Common for function data | Common for module-level names |
Output:
40
Name Resolution — LEGB
Python resolves names using the LEGB rule: Local → Enclosing → Global → Built-in.
price = 200
def show_price():
price = 300
print(price)
show_price() # 300
Python finds the local price first.
Output:
300
global Keyword
The global keyword allows a function to assign to a module-level variable.
order_count = 0
def create_order():
global order_count
order_count += 1
create_order()
print(order_count) # 1
Without global, assigning to order_count inside the function raises
UnboundLocalError, because Python treats an assigned name as local unless
declared global.
Do not overuse global state — it creates hidden dependencies and hard-to-debug programs. Prefer parameters and return values:
def increase_order_count(order_count):
return order_count + 1
order_count = 0
order_count = increase_order_count(order_count)
print(order_count)
Output:
1
Functional Tools
Lambda and Anonymous Functions
A lambda expression creates a small function using a single expression.
lambda parameters: expression
Example:
calculate_total = lambda price, quantity: price * quantity
print(calculate_total(200, 2)) # 400
Normal function vs lambda — both compute the same result:
def calculate_total(price, quantity):
return price * quantity
calculate_total = lambda price, quantity: price * quantity
An anonymous function is used without a normal def-based name and can be
passed directly to another function:
prices = [100, 200, 300]
result = map(lambda price: price * 1.05, prices)
Use lambda for small functions, simple transformations, sorting keys, map(),
filter(), and short callbacks. Avoid it for complex logic:
process = lambda x: "High" if x > 1000 else "Medium" if x > 500 else "Low"
A normal function is clearer:
def classify_price(price):
if price > 1000:
return "High"
if price > 500:
return "Medium"
print(return "Low")
Output:
SMALL ONE-EXPRESSION FUNCTION → lambda
COMPLEX LOGIC → def
Recursion
Recursion happens when a function calls itself. It needs a stopping condition.
def process_countdown(count):
if count == 0:
print("Order Processing Started")
return
print(count)
process_countdown(count - 1)
process_countdown(3)
Output:
3
2
1
Order Processing Started
The condition that stops recursion is the base case; the part that calls the function again is the recursive case.
if count == 0: # base case → STOP
return
process_countdown(count - 1) # recursive case → CALL AGAIN
Recursion without a base case eventually raises RecursionError:
def process():
process()
Factorial using recursion:
def factorial(number):
if number <= 1:
return 1
return number * factorial(number - 1)
print(factorial(5)) # 120
Recursion is useful for naturally recursive problems — tree traversal, folder traversal, graph algorithms, divide-and-conquer, and recursive data structures.
Output:
120
Built-in Functions for Data Processing
Python provides several built-in functions for processing iterable data.
map → TRANSFORM
filter → SELECT
zip → COMBINE
enumerate → COUNTER + ITEM
map()
map() applies a function to every item in an iterable.
prices = [100, 200, 300]
updated_prices = list(map(lambda price: price * 1.05, prices))
print(updated_prices) # [105.0, 210.0, 315.0]
map(function, iterable)
In Python 3, map() returns a map iterator, not a list — convert with
list():
prices = ["100", "200", "300"]
prices = list(map(int, prices))
print(prices) # [100, 200, 300]
Uses: update every price, convert strings to lowercase, convert string numbers to integers, transform API values, normalize data.
Output:
[100, 200, 300]
filter()
filter() keeps items that satisfy a condition.
prices = [100, 500, 50, 800, 200]
selected_prices = list(filter(lambda price: price >= 200, prices))
print(selected_prices) # [500, 800, 200]
numbers = [10, 15, 20, 25]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers)
Output:
[10, 20]
Like map(), filter() returns a filter iterator in Python 3 — convert
with list(). Uses: select available products, active users, passed students,
valid records, expensive products.
Output:
filter() → KEEP MATCHING ITEMS
map() vs filter()
map() |
filter() |
|---|---|
| Transforms items | Selects items |
| Applies logic to every item | Keeps matching items |
| Usually changes values | Usually reduces items |
map → CHANGE / TRANSFORM
filter → CHECK / SELECT
zip()
zip() combines items from multiple iterables by position.
foods = ["Burger", "Pizza", "Pasta"]
prices = [200, 300, 250]
menu = zip(foods, prices)
print(list(menu))
# [('Burger', 200), ('Pizza', 300), ('Pasta', 250)]
for food, price in zip(foods, prices):
print(food, "₹", price)
When lengths differ, normal zip() stops at the shortest iterable:
foods = ["Burger", "Pizza", "Pasta"]
prices = [200, 300]
print(list(zip(foods, prices)))
# [('Burger', 200), ('Pizza', 300)] ← "Pasta" excluded
Output:
[('Burger', 200), ('Pizza', 300)]
Modern Python supports strict=True, which raises ValueError when lengths
differ. This is available in Python 3.10 and later, and is useful when unequal
lengths indicate a data problem:
colors = ["red", "blue", "green"]
prices = [100, 200, 350]
foods = ["Burger", "Pizza", "Pasta"]
foods = ["Burger", "Pizza", "Pasta"]
menu = zip(foods, prices, strict=True) # ValueError if lengths differ
enumerate()
enumerate() provides a counter and an item while iterating.
foods = ["Burger", "Pizza", "Pasta"]
for number, food in enumerate(foods):
print(number, food)
# 0 Burger / 1 Pizza / 2 Pasta
The counter starts at 0 by default; use start=1 for user-facing lists:
foods = ["Burger", "Pizza", "Pasta"]
for number, food in enumerate(foods, start=1):
print(f"{number}. {food}")
# 1. Burger / 2. Pizza / 3. Pasta
When both counter/index and item are needed, enumerate() is clearer than
range(len()).
Output:
1. Burger
2. Pizza
3. Pasta
Processing Functions Comparison
| Function | Main Question |
|---|---|
map() |
How do I transform every item? |
filter() |
How do I keep matching items? |
zip() |
How do I combine iterables by position? |
enumerate() |
How do I get a counter and item? |
map → TRANSFORM
filter → SELECT
zip → COMBINE
enumerate → COUNTER + ITEM
Built-in vs User-defined
Built-in vs User-Defined Functions
Built-in functions are already provided by Python:
print(); len(); sum(); max(); min(); type(); input()
range(); map(); filter(); zip(); enumerate()
prices = [100, 200, 300]
print(sum(prices)) # 600 — sum() is built-in
Functions created by programmers are user-defined:
def calculate_total(price, quantity):
return price * quantity
| Built-in Function | User-Defined Function |
|---|---|
| Provided by Python | Created by programmer |
| Ready to use | Must be defined |
print() |
calculate_total() |
len() |
process_order() |
sum() |
apply_discount() |
Output:
PYTHON PROVIDED IT → BUILT-IN FUNCTION
PROGRAMMER CREATED IT → USER-DEFINED FUNCTION
Complete Food Delivery Billing System
def calculate_food_total(*prices):
return sum(prices)
def apply_discount(total, discount=10):
discount_amount = total * discount / 100
return total - discount_amount
def add_delivery_fee(total, delivery_fee=40):
return total + delivery_fee
def show_order(**details):
print("\nOrder Details")
for key, value in details.items():
print(key, ":", value)
food_total = calculate_food_total(200, 300, 150)
discounted_total = apply_discount(food_total)
final_bill = add_delivery_fee(discounted_total)
show_order(
customer="Arun",
payment="UPI",
status="Confirmed"
)
print("Food Total:", food_total)
print("After Discount:", discounted_total)
print("Final Bill:", final_bill)
Output:
Order Details
customer : Arun
payment : UPI
status : Confirmed
Food Total: 650
After Discount: 585.0
Final Bill: 625.0
Flow:
200, 300, 150 → calculate_food_total() → 650
→ apply_discount() → 585
→ add_delivery_fee() → 625 (FINAL BILL)
Each function performs one clear responsibility.
Function Design in Real Projects
Beginners often create one large function responsible for many tasks:
def process_order():
# calculate food total
# apply discount
# calculate tax
# add delivery fee
# process payment
# update database
# send email
print(pass)
Output:
ONE LARGE FUNCTION → TOO MANY RESPONSIBILITIES
→ HARDER TO UNDERSTAND / TEST / DEBUG
Cleaner design — one function, one clear job:
def calculate_total():
pass
def apply_discount():
pass
def process_payment():
pass
def update_order():
pass
def send_notification():
print(pass)
Small, focused functions are easier to understand, test, debug, reuse, and maintain. The goal is a clear responsibility — do not split functions mechanically into tiny pieces that provide no design value.
Output:
ONE FUNCTION → ONE CLEAR JOB
Common Fresher Mistakes
| ✗ Mistake | ✓ Correct Understanding |
|---|---|
| Defining a function but not calling it | Call the function using () |
| Thinking definition executes the function | Definition creates; calling executes |
| Confusing parameters and arguments | Parameter receives; argument sends |
| Wrong positional argument order | Position matters |
| Default parameter before required parameter | Required parameters come first |
Using [] or {} as a default value |
Use None and create the collection inside the function |
Thinking *args is a list |
*args is collected into a tuple |
Thinking **kwargs is a tuple |
**kwargs is collected into a dictionary |
Using print() instead of return for reusable results |
Use return |
Writing code after unconditional return |
return ends that function path |
| Accessing a local variable outside its scope | Understand local scope |
Overusing global |
Prefer parameters and return values |
| Recursion without a base case | Add a stopping condition |
Thinking map() selects items |
map() transforms |
Thinking filter() transforms items |
filter() selects |
Thinking map() directly returns a list |
It returns a map iterator |
Thinking filter() directly returns a list |
It returns a filter iterator |
Expecting normal zip() to keep all unequal items |
It stops at the shortest iterable |
Using range(len()) every time |
Use enumerate() when counter and item are needed |
| Using lambda for complex logic | Prefer def |
| Using recursion for simple repetition | Consider a loop |
Placement Quick Revision
FUNCTION → Reusable block of code
def → Define a function
() → Call a function
PARAMETER → Receives a value
ARGUMENT → Sends a value
return → Gives a value back
print → Shows a value
POSITIONAL → Position matters
KEYWORD → Parameter name
DEFAULT → Fallback value
*args → Extra positional arguments → Tuple
**kwargs → Extra keyword arguments → Dictionary
LOCAL → Function scope
GLOBAL → Module scope
LEGB → Local → Enclosing → Global → Built-in
global → Assign to module-level variable
lambda → Small one-expression function
RECURSION → Function calls itself
BASE CASE → Stops recursion
map → Transform
filter → Select
zip → Combine by position
enumerate → Counter + item
Interview Questions
- What is a function?
- A function is a reusable block of code designed to perform a specific task.
- What does
defdo? defis used to define a function.- What is function calling?
- Executing a function using its name and parentheses.
- What is the difference between defining and calling a function?
- Defining creates the function. Calling executes the function.
- What is a parameter?
- A parameter is a name in a function definition that receives a value.
- What is an argument?
- An argument is an actual value passed during a function call.
- What is the difference between a parameter and an argument?
- A parameter receives data in the function definition. An argument sends an actual value during the call.
- What does
returndo? returnsends a result from a function back to the caller.- What is the difference between
print()andreturn? print()displays a value.returngives a value back to the caller.- What does a function return without an explicit
return? None.- Does
returnstop a function? - Yes. It ends the current function call when execution reaches it.
- What are positional arguments?
- Arguments matched to parameters based on position.
- What are keyword arguments?
- Arguments passed using parameter names.
- What is a default parameter value?
- A predefined value used when the caller omits the corresponding argument.
- What is
*args? - It collects extra positional arguments into a tuple.
- Is the name
argsmandatory? - No. The
*creates variable positional argument behavior;argsis a naming convention. - What is
**kwargs? - It collects extra keyword arguments into a dictionary.
- What is the difference between
*argsand**kwargs? *argscollects extra positional arguments into a tuple.**kwargscollects extra keyword arguments into a dictionary.- What is scope?
- Scope defines where a name can be accessed or resolved.
- What is a local variable?
- A variable created inside a function and belonging to its local scope.
- What is the LEGB rule?
- Local, Enclosing, Global, Built-in.
- What does
globaldo? - It allows assignment to a module-level variable from inside a function.
- What is a lambda function?
- A small function created using a lambda expression containing one expression.
- What is an anonymous function?
- A function used without a normal
def-based name. - What is recursion?
- Recursion occurs when a function calls itself.
- What is a base case?
- The condition that stops recursion.
- What is a recursive case?
- The part that calls the function again.
- What does
map()do? - It applies a function to every item in an iterable.
- Does
map()directly return a list in Python 3? - No. It returns a map iterator.
- What does
filter()do? - It keeps items that satisfy a condition.
- What is the difference between
map()andfilter()? map()transforms items.filter()selects items.- What does
zip()do? - It combines items from multiple iterables by position.
- What happens when normal
zip()receives iterables of different lengths? - It stops when the shortest iterable is exhausted.
- What does
enumerate()do? - It provides a counter and item while iterating.
- What is the difference between built-in and user-defined functions?
- Built-in functions are provided by Python. User-defined functions are created by programmers.
Practice
Use a Food Delivery Order System.
- Create a function named
show_order(). - Define and call
show_order(). - Create
calculate_total(price, quantity). - Identify
priceandquantityas parameters. - Pass
200and2as arguments. - Return the calculated total.
- Store the returned total in a variable.
- Explain
print()vsreturn. - Call a function using positional arguments.
- Call a function using keyword arguments.
- Explain why positional order matters.
- Add a default delivery fee of
40. - Override the default delivery fee.
- Create a function accepting multiple prices using
*args. - Calculate the sum of all
*argsvalues. - Check the type of
args. - Create an order-details function using
**kwargs. - Display every
**kwargskey and value. - Check the type of
kwargs. - Explain
*argsvs**kwargs. - Create a local variable inside a function.
- Try accessing the local variable outside the function.
- Explain the LEGB rule.
- Update a module-level order count using
global. - Rewrite the order counter using parameters and
return. - Create a lambda to add 5% tax.
- Explain when lambda should be avoided.
- Create a recursive order countdown.
- Identify the base case.
- Identify the recursive case.
- Create a recursive factorial function.
- Explain why recursion without a base case is dangerous.
- Add 5% tax to every price using
map(). - Convert string prices to integers using
map(). - Select prices greater than or equal to ₹200 using
filter(). - Explain
map()vsfilter(). - Combine food names and prices using
zip(). - Test
zip()with unequal iterable lengths. - Display menu numbers using
enumerate(). - Start
enumerate()from1. - Rewrite a
range(len())loop usingenumerate(). - Explain built-in vs user-defined functions.
Predict the output:
def calculate(price, quantity):
return price * quantity
print(calculate(200, 2))
def show():
print("Order")
result = show()
print(result)
def show(*args):
print(type(args))
show(10, 20, 30)
def show(**kwargs):
print(type(kwargs))
show(name="Arun")
def calculate():
return 400
print("Completed")
print(calculate())
Find the mistake:
def calculate(delivery_fee=40, price):
return price + delivery_fee
def process():
process()
def calculate_total():
total = 400
calculate_total()
print(total)
Final task: Create a complete Food Delivery Billing System using separate
functions for food total, discount, delivery fee, order details, and final bill.
Use parameters, arguments, return, default values, *args, and **kwargs.
Output:
ONE FUNCTION → ONE CLEAR JOB
End of Level 6