Skip to content

Functions

A function is a reusable block of code with a name. Define once, use many times.

Defining a function

def greet():
    print("Hello, World!")

# Call the function
greet()
greet()
greet()

def defines a function. The indented block is its body. The function doesn't run until you call it with ().

Functions with parameters

Parameters are inputs to the function.

def greet(name):
    print(f"Hello, {name}!")

greet("Alice")
greet("Bob")

Multiple parameters

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

result = add(5, 3)
print(result)         # 8
print(add(10, 20))    # 30

return sends a value back to the caller. Without return, a function returns None.

Default parameter values

Make a parameter optional by giving it a default:

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")                       # Hello, Alice!
greet("Bob", "Hi")                   # Hi, Bob!
greet("Charlie", greeting="Yo")      # Yo, Charlie!

Keyword arguments

Pass arguments by name — order doesn't matter:

def create_user(name, age, city):
    print(f"{name}, {age} years old, lives in {city}")

# Positional — order matters
create_user("Alice", 25, "Mumbai")

# Keyword — clearer, order doesn't matter
create_user(age=25, city="Mumbai", name="Alice")

Variable-length arguments — *args and **kwargs

When you don't know how many arguments will be passed:

# *args collects extra positional args as a tuple
def add_all(*numbers):
    return sum(numbers)

print(add_all(1, 2, 3))               # 6
print(add_all(1, 2, 3, 4, 5, 10))     # 25

# **kwargs collects extra keyword args as a dict
def print_info(**details):
    for key, value in details.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25, city="Mumbai")

Returning multiple values

def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print(f"Min: {low}, Max: {high}")

You're really returning a tuple that gets unpacked.

Docstrings — document your function

A string right after def is the function's docstring. help() shows it.

def area_of_circle(radius):
    """
    Return the area of a circle.

    Args:
        radius: the radius of the circle in any unit

    Returns:
        the area in unit²
    """
    return 3.14159 * radius * radius

print(area_of_circle(5))
help(area_of_circle)

Scope — local vs global variables

Variables inside a function are local — they only exist inside that function.

x = 10        # global

def show():
    x = 99    # local — different from the global x
    print("inside:", x)

show()
print("outside:", x)

To modify a global from inside a function, use global:

count = 0

def increment():
    global count
    count += 1

increment()
increment()
increment()
print(count)    # 3

Avoid global when possible. Better: return the value and let the caller assign it.

Recursion — a function that calls itself

A classic example — computing factorial: n! = n × (n-1)!

def factorial(n):
    if n <= 1:
        return 1                          # base case
    return n * factorial(n - 1)           # recursive case

print(factorial(5))    # 5*4*3*2*1 = 120
print(factorial(7))    # 5040

Every recursive function needs: 1. Base case — stops the recursion. 2. Recursive case — calls itself with a smaller problem.

Without a base case → infinite recursion → RecursionError.

lambda — anonymous one-liner functions

lambda parameters: expression — a function without a name, useful as a one-time argument.

# Same as: def square(x): return x * x
square = lambda x: x * x
print(square(5))     # 25

# Most useful when passing to another function
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
sorted_desc = sorted(numbers, key=lambda x: -x)
print(sorted_desc)

map, filter, reduce

Three functional-programming building blocks.

map — apply a function to every item:

numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x * x, numbers))
print(squares)    # [1, 4, 9, 16, 25]

# Same with a list comprehension (more Pythonic)
squares2 = [x * x for x in numbers]
print(squares2)

filter — keep only items where the function returns True:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)    # [2, 4, 6, 8, 10]

# Same with comprehension
evens2 = [x for x in numbers if x % 2 == 0]
print(evens2)

reduce — combine all items into one value (need to import):

from functools import reduce

numbers = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, numbers)
print(total)    # 1+2+3+4+5 = 15

product = reduce(lambda acc, x: acc * x, numbers)
print(product)    # 1*2*3*4*5 = 120

Practice

What does this print?

Expected: Hi, Alice!

def greet(name, greeting="Hi"):
    return f"{greeting}, {name}!"

print(greet("Alice"))

Return the sum of all args (not just the first one)

Expected: 10

def total(*numbers):
    return numbers[0]        # bug: this only returns the first
print(total(1, 2, 3, 4))

Quiz — Quick check

What you remember

Q1. What does a function return if you don't write a return statement?

  • 0
  • False
  • None
  • An error

Why: Every Python function implicitly returns None if no return is reached.

Q2. What does *args collect arguments into?

  • A list
  • A tuple
  • A dict
  • A set

Why: *args packs extra positional arguments into a tuple. **kwargs packs extra keyword arguments into a dict.

Q3. A recursive function must have a…

  • return of None
  • global declaration
  • base case
  • lambda form

Why: Without a base case (a stopping condition), the function keeps calling itself until Python raises RecursionError.

Common doubts

What's the difference between a parameter and an argument?

A parameter is the name in the function definition (def add(a, b)a and b). An argument is the value you pass when calling (add(5, 3)5 and 3). People often use the terms interchangeably; in interviews you'll hear "parameters" for definitions and "arguments" for calls.

When should I use lambda?

Use lambda when you need a tiny throwaway function inline — typically as the key= argument to sorted, max, min, or with map/filter. For anything multi-line or reused, write a real def — it's more readable and easier to debug.

Are default arguments evaluated once or every call?

Once, when the function is defined. This causes a classic bug:

def add_to(item, target=[]):   # ← target is shared across calls!
    target.append(item)
    return target
Calling add_to(1) twice gives [1, 1], not [1] each time. Use target=None and create [] inside the function instead.

What's next

Strings