Checking your session…
Module 04: Control Flow & Functions

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.

1. Call add(3, 4) 2. Parameters a = 3, b = 4 3. Body runs result = a + b 4. Return return result caller value 7 returned to caller
How a function call works: arguments go in, the body runs, and a value is returned to the caller.
PYTHON
def calculate_total():
    price = 200
    quantity = 2
    total = price * quantity
    print(total)

calculate_total()

Output:

TEXT
400

Defining & Calling

A function lets us write logic once and reuse it many times.

Without a function, the same calculation is repeated:

PYTHON
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:

PYTHON
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:

TEXT
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:

TEXT
INPUT → FUNCTION → PROCESS → OUTPUT
PYTHON
def calculate_total(price, quantity):
    return price * quantity

print(calculate_total(200, 2))

Output:

TEXT
400

Defining a Function

Creating a function is called defining it. Python uses the def keyword.

PYTHON
def function_name():
    statement

Example:

PYTHON
def show_order():
    print("Order Received")

Output:

TEXT
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

PYTHON
def show_order():
    print("Order Received")

Output: no output. The function was only defined, not executed.

TEXT
def → DEFINE / CREATE FUNCTION (not execute)

Calling a Function

Executing a function is called calling it — the function name followed by parentheses.

PYTHON
def show_order():
    print("Order Received")

show_order()

Output:

TEXT
Order Received

How calling works:

PYTHON
def show_order():
    print("Order Received")

print("Start")
show_order()
print("End")

Output:

TEXT
Start
Order Received
End
TEXT
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
TEXT
def → DEFINE      () → CALL
DEFINE ≠ EXECUTE  CALL → EXECUTE

Parameters and Arguments

PYTHON
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:

TEXT
400
Parameter Argument
Name in function definition Actual value passed to function
Receives data Sends data
price 200
quantity 2
TEXT
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.

PYTHON
def place_order(food, quantity):
    print("Food:", food)
    print("Quantity:", quantity)

place_order("Burger", 2)

Output:

TEXT
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:

PYTHON
def place_order(food, quantity):
place_order(2, "Burger")   # food ← 2, quantity ← "Burger"

Output:

TEXT
POSITIONAL ARGUMENT → POSITION MATTERS

Keyword Arguments

Keyword arguments pass values using parameter names, so order can change.

PYTHON
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:

TEXT
Food: Burger
Quantity: 2
Food: Burger
Quantity: 2

Default Arguments

A parameter can have a predefined default value, used when the caller omits it.

PYTHON
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:

PYTHON
def calculate_bill(price, delivery_fee=40):
    pass

Wrong (causes SyntaxError):

PYTHON
def calculate_bill(delivery_fee=40, price):
    pass

Output:

TEXT
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.

PYTHON
def calculate_order(*prices):
    print(prices)

calculate_order(200, 150, 300)   # (200, 150, 300)

Sum multiple prices:

PYTHON
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:

TEXT
200
350
650

Variable-Length Keyword Arguments — **kwargs

**kwargs allows a function to accept a variable number of keyword arguments, collected into a dictionary.

PYTHON
def create_order(**details):
    print(details)

create_order(customer="Arun", food="Burger", quantity=2)

Output:

TEXT
{'customer': 'Arun', 'food': 'Burger', 'quantity': 2}

Access values and iterate:

PYTHON
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:

TEXT
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")
* **
TEXT
*args    → VALUES     → TUPLE
**kwargs → KEY=VALUE  → DICTIONARY

Scope & Return

Return Values

The return statement sends a result from a function back to the caller.

PYTHON
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:

PYTHON
def calculate_total(price, quantity):
    return price * quantity

total = calculate_total(200, 2)
final_bill = total + 40
print(final_bill)

Output:

TEXT
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
TEXT
print  → SHOW VALUE
return → GIVE VALUE BACK

return Stops the Function

PYTHON
def calculate_total():
    return 400
    print("Completed")   # unreachable

Code after an unconditional return in the same execution path is unreachable.

Output:

TEXT
return → FUNCTION CALL ENDS

Function Without Explicit return

PYTHON
def show_order():
    print("Order Received")

result = show_order()
print(result)

Output:

TEXT
Order Received
None

With no explicit return, Python returns None.

TEXT
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:

PYTHON
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:

PYTHON
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:

TEXT
40

Name Resolution — LEGB

Python resolves names using the LEGB rule: Local → Enclosing → Global → Built-in.

PYTHON
price = 200

def show_price():
    price = 300
    print(price)

show_price()   # 300

Python finds the local price first.

Output:

TEXT
300

global Keyword

The global keyword allows a function to assign to a module-level variable.

PYTHON
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:

PYTHON
def increase_order_count(order_count):
    return order_count + 1

order_count = 0
order_count = increase_order_count(order_count)
print(order_count)

Output:

TEXT
1

Functional Tools

Lambda and Anonymous Functions

A lambda expression creates a small function using a single expression.

PYTHON
lambda parameters: expression

Example:

PYTHON
calculate_total = lambda price, quantity: price * quantity
print(calculate_total(200, 2))   # 400

Normal function vs lambda — both compute the same result:

PYTHON
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:

PYTHON
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:

PYTHON
process = lambda x: "High" if x > 1000 else "Medium" if x > 500 else "Low"

A normal function is clearer:

PYTHON
def classify_price(price):
    if price > 1000:
        return "High"
    if price > 500:
        return "Medium"
print(return "Low")

Output:

TEXT
SMALL ONE-EXPRESSION FUNCTION → lambda
COMPLEX LOGIC                  → def

Recursion

Recursion happens when a function calls itself. It needs a stopping condition.

PYTHON
def process_countdown(count):
    if count == 0:
        print("Order Processing Started")
        return
    print(count)
    process_countdown(count - 1)

process_countdown(3)

Output:

TEXT
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.

PYTHON
if count == 0:                 # base case → STOP
    return
process_countdown(count - 1)   # recursive case → CALL AGAIN

Recursion without a base case eventually raises RecursionError:

PYTHON
def process():
    process()

Factorial using recursion:

PYTHON
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:

TEXT
120

Built-in Functions for Data Processing

Python provides several built-in functions for processing iterable data.

TEXT
map       → TRANSFORM
filter    → SELECT
zip       → COMBINE
enumerate → COUNTER + ITEM

map()

map() applies a function to every item in an iterable.

PYTHON
prices = [100, 200, 300]
updated_prices = list(map(lambda price: price * 1.05, prices))
print(updated_prices)   # [105.0, 210.0, 315.0]
PYTHON
map(function, iterable)

In Python 3, map() returns a map iterator, not a list — convert with list():

PYTHON
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:

TEXT
[100, 200, 300]

filter()

filter() keeps items that satisfy a condition.

PYTHON
prices = [100, 500, 50, 800, 200]
selected_prices = list(filter(lambda price: price >= 200, prices))
print(selected_prices)   # [500, 800, 200]
PYTHON
numbers = [10, 15, 20, 25]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers)

Output:

TEXT
[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:

TEXT
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
TEXT
map    → CHANGE / TRANSFORM
filter → CHECK / SELECT

zip()

zip() combines items from multiple iterables by position.

PYTHON
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:

PYTHON
foods = ["Burger", "Pizza", "Pasta"]
prices = [200, 300]
print(list(zip(foods, prices)))
# [('Burger', 200), ('Pizza', 300)]   ← "Pasta" excluded

Output:

TEXT
[('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:

PYTHON
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.

PYTHON
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:

PYTHON
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:

TEXT
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?
TEXT
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:

PYTHON
print(); len(); sum(); max(); min(); type(); input()
range(); map(); filter(); zip(); enumerate()
PYTHON
prices = [100, 200, 300]
print(sum(prices))   # 600  — sum() is built-in

Functions created by programmers are user-defined:

PYTHON
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:

TEXT
PYTHON PROVIDED IT    → BUILT-IN FUNCTION
PROGRAMMER CREATED IT → USER-DEFINED FUNCTION

Complete Food Delivery Billing System

PYTHON
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:

TEXT
Order Details
customer : Arun
payment : UPI
status : Confirmed
Food Total: 650
After Discount: 585.0
Final Bill: 625.0

Flow:

TEXT
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:

PYTHON
def process_order():
    # calculate food total
    # apply discount
    # calculate tax
    # add delivery fee
    # process payment
    # update database
    # send email
print(pass)

Output:

TEXT
ONE LARGE FUNCTION → TOO MANY RESPONSIBILITIES
→ HARDER TO UNDERSTAND / TEST / DEBUG

Cleaner design — one function, one clear job:

PYTHON
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:

TEXT
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

TEXT
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 def do?
def is 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 return do?
return sends a result from a function back to the caller.
What is the difference between print() and return?
print() displays a value. return gives a value back to the caller.
What does a function return without an explicit return?
None.
Does return stop 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 args mandatory?
No. The * creates variable positional argument behavior; args is a naming convention.
What is **kwargs?
It collects extra keyword arguments into a dictionary.
What is the difference between *args and **kwargs?
*args collects extra positional arguments into a tuple. **kwargs collects 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 global do?
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() and filter()?
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.

  1. Create a function named show_order().
  2. Define and call show_order().
  3. Create calculate_total(price, quantity).
  4. Identify price and quantity as parameters.
  5. Pass 200 and 2 as arguments.
  6. Return the calculated total.
  7. Store the returned total in a variable.
  8. Explain print() vs return.
  9. Call a function using positional arguments.
  10. Call a function using keyword arguments.
  11. Explain why positional order matters.
  12. Add a default delivery fee of 40.
  13. Override the default delivery fee.
  14. Create a function accepting multiple prices using *args.
  15. Calculate the sum of all *args values.
  16. Check the type of args.
  17. Create an order-details function using **kwargs.
  18. Display every **kwargs key and value.
  19. Check the type of kwargs.
  20. Explain *args vs **kwargs.
  21. Create a local variable inside a function.
  22. Try accessing the local variable outside the function.
  23. Explain the LEGB rule.
  24. Update a module-level order count using global.
  25. Rewrite the order counter using parameters and return.
  26. Create a lambda to add 5% tax.
  27. Explain when lambda should be avoided.
  28. Create a recursive order countdown.
  29. Identify the base case.
  30. Identify the recursive case.
  31. Create a recursive factorial function.
  32. Explain why recursion without a base case is dangerous.
  33. Add 5% tax to every price using map().
  34. Convert string prices to integers using map().
  35. Select prices greater than or equal to ₹200 using filter().
  36. Explain map() vs filter().
  37. Combine food names and prices using zip().
  38. Test zip() with unequal iterable lengths.
  39. Display menu numbers using enumerate().
  40. Start enumerate() from 1.
  41. Rewrite a range(len()) loop using enumerate().
  42. Explain built-in vs user-defined functions.

Predict the output:

PYTHON
def calculate(price, quantity):
    return price * quantity
print(calculate(200, 2))
PYTHON
def show():
    print("Order")
result = show()
print(result)
PYTHON
def show(*args):
    print(type(args))
show(10, 20, 30)
PYTHON
def show(**kwargs):
    print(type(kwargs))
show(name="Arun")
PYTHON
def calculate():
    return 400
    print("Completed")
print(calculate())

Find the mistake:

PYTHON
def calculate(delivery_fee=40, price):
    return price + delivery_fee
PYTHON
def process():
    process()
PYTHON
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:

TEXT
ONE FUNCTION → ONE CLEAR JOB

End of Level 6