Python Functions: The Complete Beginner's Guide

You have data, conditions, and loops. Now you need structure. Functions let you write logic once and reuse it everywhere. This chapter covers parameters, returns, defaults, scope, \`*args\`/\`**kwargs\`, lambda, recursion, and function design patterns.

Chapter 10 of 20 · Intermediate · 50 min · Python Programming Course

You have written programs that store data, make decisions, and loop through collections. Every concept worked - but if you look back at your code, you will notice something: the same logic appears in multiple places. A grade calculation written three times. A validation check copied and pasted. A formatting block repeated for each output.

This is the problem functions solve. A function is a named, reusable block of code. You write the logic once, give it a name, and call that name whenever you need the logic - once, a hundred times, from anywhere in your program. Change the function once and the change applies everywhere it is used.

Functions are the single most important concept for writing code that is maintainable, readable, and scalable. Every professional Python codebase is built from functions. Every library you will ever import is a collection of functions. Mastering them here makes everything from Day 11 onwards significantly easier.

What You Will Learn in This Chapter

By the end of this tutorial you will be able to:

  • Define and call functions using def
  • Write functions with parameters and return values
  • Use default parameter values
  • Pass arguments by position and by keyword
  • Write flexible functions using *args and **kwargs
  • Return multiple values from a single function
  • Understand variable scope - local vs global
  • Write lambda functions for concise one-liners
  • Write recursive functions
  • Apply the guard clause and single responsibility patterns
  • Avoid the most common function mistakes beginners make

Estimated time: 50 minutes reading + 25 minutes practice

Defining and Calling a Function

A function definition uses the def keyword, followed by the function name, parentheses, and a colon. The indented block below is the function body - the code that runs when the function is called:

def greet():
    print("Hello, World!")
    print("Welcome to Python functions")

greet()    # call the function
greet()    # call it again - same code, no duplication

Output:

Hello, World!
Welcome to Python functions
Hello, World!
Welcome to Python functions

Defining a function does not run it. def greet(): just registers the function with Python. The function body only executes when you call greet().

Function naming rules:

  • Use lowercase with underscores: calculate_tax, get_user_name, is_valid_email
  • Name describes what the function does - a verb or verb phrase
  • Be specific: calculate_monthly_tax beats calc or do_thing
  • Python convention is snake_case - same as variable names

Parameters and Arguments

A parameter is a variable declared in the function definition. An argument is the actual value you pass when calling the function. The terms are often used interchangeably in conversation, but the distinction matters for precision:

def greet(name):          # name is the PARAMETER
    print(f"Hello, {name}!")

greet("Alice")            # "Alice" is the ARGUMENT
greet("Bob")
greet("Priya")

Output:

Hello, Alice!
Hello, Bob!
Hello, Priya!

The same function, three different inputs, three different outputs. This is the core value proposition of functions - write logic once, apply it to any input.

Multiple Parameters

def describe_student(name, age, course):
    print(f"Name: {name}")
    print(f"Age:  {age}")
    print(f"Course: {course}")
    print()

describe_student("Priya", 21, "Python Programming")
describe_student("Arjun", 19, "Data Science")

Return Values - Getting Results Back

A function that only prints output is useful for display but limited for computation. The return statement sends a value back to the caller, where it can be stored, used in expressions, or passed to other functions:

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

result = add(10, 5)
print(result)              # 15
print(add(3, 7) * 2)       # 20
print(add(add(1, 2), 3))   # 6

Functions Without return

A function that does not have a return statement returns None implicitly:

def print_greeting(name):
    print(f"Hello, {name}!")    # only prints, returns nothing

result = print_greeting("Alice")
print(result)    # None

Return Exits the Function Immediately

Once Python hits a return statement, the function ends. Any code after return in the same block does not run:

def check_age(age):
    if age < 0:
        return "Invalid age"
    if age < 18:
        return "Minor"
    return "Adult"

print(check_age(-5))    # Invalid age
print(check_age(15))    # Minor
print(check_age(25))    # Adult

This is the guard clause pattern from Day 8 applied to functions. Handle edge cases with early returns at the top, then write the main logic without nesting.

Returning Multiple Values

Python functions can return multiple values by separating them with commas. Python packs them into a tuple automatically:

def get_stats(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

low, high, avg = get_stats([88, 92, 79, 95, 83])
print(f"Min: {low}, Max: {high}, Average: {avg:.1f}")
def parse_name(full_name):
    parts = full_name.strip().split()
    if len(parts) >= 2:
        return parts[0], " ".join(parts[1:])
    return full_name, ""

first, last = parse_name("Priya Sharma")
print(f"First: {first}, Last: {last}")

The return value is a tuple - you can unpack it into individual variables or keep it as a tuple:

result = get_stats([88, 92, 79])
print(result)            # (79, 92, 86.33...)
print(result[0])         # 79
low, high, avg = result  # unpack

Default Parameters

Default parameters give a parameter a fallback value when no argument is provided:

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

greet("Alice")
greet("Bob", "Good morning")
greet("Carol", "Hey")

Rules for Default Parameters

Default parameters must come after non-default parameters:

def create_profile(name, age, city="Unknown", is_active=True):
    return {
        "name": name,
        "age": age,
        "city": city,
        "active": is_active
    }

The Mutable Default Argument Trap

This is one of the most famous Python gotchas:

# Wrong - mutable default argument is created ONCE and shared
def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item("apple"))     # ['apple']
print(add_item("banana"))    # ['apple', 'banana'] - unexpected!
print(add_item("cherry"))    # ['apple', 'banana', 'cherry'] - wrong!

The default list [] is created once when the function is defined - not each time it is called. Every call that uses the default shares the same list object.

The fix - use None as the default:

def add_item(item, items=None):
    if items is None:
        items = []    # create a fresh list every time
    items.append(item)
    return items

print(add_item("apple"))     # ['apple']
print(add_item("banana"))    # ['banana'] - correct

Always use None as the default for mutable parameters (lists, dicts, sets). This mistake catches experienced developers too - know it before you encounter it.

Positional and Keyword Arguments

Positional Arguments - Order Matters

def describe(name, age, city):
    print(f"{name}, {age}, from {city}")

describe("Alice", 25, "Delhi")    # correct
describe(25, "Alice", "Delhi")    # wrong order, no error but wrong meaning

Keyword Arguments - Name Matters

describe(age=25, city="Delhi", name="Alice")
describe("Alice", city="Delhi", age=25)

Rules for mixing:

  • Positional arguments must come before keyword arguments
  • Each parameter receives exactly one value
describe("Alice", city="Delhi", age=25)    # valid
describe(name="Alice", 25, "Delhi")        # SyntaxError

Keyword-Only Arguments - Forcing Explicit Names

Place * in the parameter list to force all following parameters to be keyword-only:

def create_user(name, *, role="viewer", is_active=True):
    return {"name": name, "role": role, "active": is_active}

create_user("Alice")
create_user("Bob", role="admin")

*args - Variable Number of Positional Arguments

*args allows a function to accept any number of positional arguments. Inside the function, args is a tuple:

def total(*args):
    print(f"Arguments received: {args}")
    return sum(args)

print(total(1, 2, 3))
print(total(10, 20, 30, 40))

**kwargs - Variable Number of Keyword Arguments

**kwargs allows a function to accept any number of keyword arguments. Inside the function, kwargs is a dictionary:

def display_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

display_info(name="Alice", age=25, city="Delhi", role="admin")

Variable Scope - Where Variables Live

Scope determines where a variable is accessible.

Local Scope - Variables Inside Functions

def calculate():
    result = 42
    print(result)

calculate()
print(result)    # NameError

Global Scope - Variables Outside Functions

MAX_SCORE = 100

def check_score(score):
    if score > MAX_SCORE:
        return f"Invalid: exceeds maximum of {MAX_SCORE}"
    return f"Valid score: {score}"

Reading vs Modifying Global Variables

count = 0

def increment():
    global count
    count += 1

Why avoid globals: functions that modify global state are harder to test, debug, and reason about. The clean alternative is to pass the value in and return the updated value.

The LEGB Rule

When Python encounters a name, it searches in this order:

Local -> Enclosing -> Global -> Built-in

x = "global"

def outer():
    x = "enclosing"

    def inner():
        x = "local"
        print(x)

    inner()
    print(x)

outer()
print(x)

Lambda Functions - One-Line Anonymous Functions

A lambda is a small anonymous function defined in a single expression:

square = lambda x: x ** 2
print(square(5))    # 25

Use lambdas mainly as short callbacks:

students = [
    {"name": "Alice", "score": 88},
    {"name": "Bob", "score": 95},
    {"name": "Carol", "score": 72},
]

ranked = sorted(students, key=lambda s: s["score"], reverse=True)

Docstrings - Documenting Your Functions

A docstring is a string literal written as the first statement in a function body:

def calculate_bmi(weight_kg, height_m):
    """
    Calculate Body Mass Index (BMI).

    Args:
        weight_kg: Weight in kilograms (float)
        height_m: Height in metres (float)

    Returns:
        float: BMI value rounded to 1 decimal place
    """
    bmi = weight_kg / (height_m ** 2)
    return round(bmi, 1)

Recursive Functions - Functions That Call Themselves

A recursive function calls itself within its own body:

def factorial(n):
    if n == 0 or n == 1:
        return 1
    return n * factorial(n - 1)

Every recursive function needs:

  • A base case that stops recursion
  • A recursive case that reduces the problem

Function Design Principles

Single Responsibility - One Function, One Job

A well-designed function does one thing clearly. Split large multi-purpose functions into small, focused units.

Pure Functions - Predictable and Testable

A pure function returns the same output for the same input and avoids side effects.

A Complete Working Program

Here is a student grade management system built entirely from well-designed functions:

def calculate_average(scores):
    """Return the average of a list of scores."""
    if not scores:
        return 0
    return sum(scores) / len(scores)

def assign_grade(average):
    """Return letter grade for a given average."""
    if average >= 90: return "A"
    if average >= 80: return "B"
    if average >= 70: return "C"
    if average >= 60: return "D"
    return "F"

def is_passing(average, threshold=60):
    """Return True if average meets the passing threshold."""
    return average >= threshold

def format_student_result(name, scores):
    """Return a formatted result dictionary for one student."""
    avg = calculate_average(scores)
    grade = assign_grade(avg)
    status = "Pass" if is_passing(avg) else "Fail"
    return {
        "name": name,
        "scores": scores,
        "average": avg,
        "grade": grade,
        "status": status
    }

def generate_class_report(student_data):
    """Generate full report for all students."""
    results = [
        format_student_result(name, scores)
        for name, scores in student_data.items()
    ]
    passing = [r for r in results if r["status"] == "Pass"]
    failing = [r for r in results if r["status"] == "Fail"]
    all_avgs = [r["average"] for r in results]
    top = max(results, key=lambda r: r["average"])

    return results, {
        "class_average": sum(all_avgs) / len(all_avgs),
        "passing_count": len(passing),
        "failing_count": len(failing),
        "top_student": top["name"],
        "top_average": top["average"]
    }

This program demonstrates function composition: small focused functions combined to produce a full report.

5 Function Mistakes Every Beginner Makes

Mistake 1: Confusing print with return

If you need to use a function's output in further calculations, it must return - not print.

Mistake 2: Mutable default argument

Never use mutable defaults like [] or {} directly in parameters; use None instead.

Mistake 3: Modifying global state instead of returning

Prefer explicit input-output flow over hidden global mutations.

Mistake 4: Too many parameters

Too many parameters often means the function is doing too much and should be split.

Mistake 5: Ignoring the return value

Know which APIs mutate in place and which return new values.

Practice: Function Exercises

Exercise 1: Basic functions

Write three functions: celsius_to_fahrenheit(c), fahrenheit_to_celsius(f), and celsius_to_kelvin(c). Test each with several values.

Exercise 2: Default parameters

Write a function create_email(username, domain="gmail.com", extension="com") that returns a formatted email address. Call it with different combinations of arguments.

**Exercise 3: *args and kwargs

Write a function describe(*args, **kwargs) that prints all positional arguments on one line and all keyword arguments as key-value pairs below.

Exercise 4: Recursive function

Write a recursive function sum_digits(n) that returns the sum of digits of a positive integer without converting to a string.

Exercise 5: Real-world function design

Write add_item(cart, name, price, qty=1), remove_item(cart, name), calculate_total(cart), and apply_discount(total, percent). Each function should do exactly one job and return its result.

-> See all Python practice exercises with solutions

What Comes Next - Day 11: Classes and Objects

Functions organise code into reusable blocks. Classes take that idea further - they bundle related data and functions together into a single unit called an object.

Day 11 covers:

  • Defining classes with class
  • The __init__ constructor method
  • Instance attributes and methods
  • The self parameter
  • Class attributes vs instance attributes
  • The __str__ method for readable object representation

-> Continue to Day 11: Classes and Objects

Frequently Asked Questions

What is a function in Python?

A function is a named, reusable block of code that performs a specific task. You define it with def, call it by name, and optionally return a value.

What is the difference between a parameter and an argument in Python?

A parameter is the variable name in the function definition. An argument is the actual value you pass during the function call.

What is the difference between return and print in a Python function?

print displays output but returns None. return sends a value back to the caller for reuse in further logic.

What are *args and **kwargs in Python?

*args collects extra positional arguments as a tuple. **kwargs collects extra keyword arguments as a dictionary.

What is variable scope in Python?

Scope controls where a variable can be accessed: local inside functions, global outside functions, with LEGB lookup rules.

What is a lambda function in Python?

A lambda is a one-expression anonymous function, useful for short throwaway callbacks in APIs like sorted(), map(), and filter().

What is a recursive function in Python?

A recursive function calls itself and must include a base case plus a recursive case to avoid infinite recursion.

What is a docstring in Python?

A docstring is a string literal at the top of a function, class, or module body used for built-in help and generated documentation.

Chapter navigation

Frequently asked questions: Python functions

What is a function in Python?

A function is a named, reusable block of code that performs a specific task. You define it once with def and call it by name whenever you need it. Functions take inputs (parameters), process them, and optionally return an output.

What is the difference between a parameter and an argument in Python?

A parameter is the variable name declared in the function definition. An argument is the actual value passed when calling the function.

What is the difference between return and print in a Python function?

print() displays a value to the screen but returns None. return sends a value back to the caller so it can be stored, reused, or composed with other functions.

What are *args and **kwargs in Python?

*args lets a function accept any number of positional arguments as a tuple. **kwargs lets it accept any number of keyword arguments as a dictionary.

What is variable scope in Python?

Scope determines where a variable is accessible. Variables inside a function are local. Variables defined outside functions are global. Python resolves names using LEGB order: Local, Enclosing, Global, Built-in.

What is a lambda function in Python?

A lambda is a small anonymous one-expression function, commonly used as a short callback in APIs like sorted(), map(), and filter().

What is a recursive function in Python?

A recursive function calls itself inside its own body and must include a base case to stop recursion plus a recursive case that moves toward that base case.

What is a docstring in Python?

A docstring is a string literal written as the first statement in a function, class, or module to explain its purpose, inputs, and outputs. It is visible through help() and __doc__.