Define and Call Functions
Learn how to define and call your own functions in Python with a step-by-step tutorial, hands-on exercise, and troubleshooting tips.
Focus: define and call your own functions
You've been writing the same three lines of code over and over just to greet a user, calculate a discount, or validate input. It's tedious, error-prone, and makes your scripts grow longer without becoming smarter. That's the problem — and the solution is simple: define and call your own functions to package logic once and reuse it everywhere.
The problem this lesson solves
Without functions, every script is a flat sequence of instructions. If you want to greet ten users, you either copy-paste the same print statements or write a loop that still forces you to write the greeting logic inline. This leads to bloated code, hidden bugs when you edit one copy but forget another, and a painful process when you need to change how a greeting works — you'd have to hunt down every occurrence.
Functions solve this: you write the logic once, give it a name, and call it by name whenever you need it. Change one place, and every callsite benefits from the fix or improvement.
Core concept / mental model
Think of a function as a mini-program inside your program. It takes optional inputs (parameters), does something with them, and optionally returns a result.
Pro tip: A function is like a recipe. You define the steps once in a cookbook, and anyone can call the recipe by name to produce the dish. The recipe doesn't care who asks for it — it just follows the instructions.
Python functions live in memory after you define them (but before you call them, they're just inert code). Calling a function executes its body, then control returns to the line after the call.
Anatomy of a function definition
def function_name(parameter1, parameter2):
"""Optional docstring explaining what the function does."""
# function body
result = parameter1 + parameter2
return result
def— keyword that starts a function definition.function_name— follows the same rules as variable names (letters, underscores, no spaces).(parameter1, parameter2)— comma-separated list of input variables. Can be empty.:— colon marks the end of the header.- Indented block (4 spaces) — the function body.
return— optional keyword that sends a value back to the caller. If omitted, the function returnsNone.
How it works step by step
Step 1: Define the function
Python reads the def statement and creates a function object, binding the name to it. Nothing executes yet.
def greet(name):
print(f"Hello, {name}!")
Step 2: Call the function
Write the function name followed by parentheses (). If the function expects arguments, provide them inside the parentheses.
greet("Alice") # Output: Hello, Alice!
greet("Bob") # Output: Hello, Bob!
Step 3: Execution flow
When Python reaches greet("Alice"):
1. It suspends execution of the current scope.
2. It assigns "Alice" to the parameter name inside the function's local scope.
3. It runs the function body — here it calls print().
4. After the body finishes (or hits return), control goes back to the line after the call.
Step 4: Return values
Use the return keyword to send data back. The caller can capture it in a variable.
def add(a, b):
return a + b
result = add(5, 3)
print(result) # Output: 8
Common gotcha: If you forget
return, the function still runs but returnsNone.
Hands-on walkthrough
Let's build a small utility library step by step.
Example 1: A simple converter
def celsius_to_fahrenheit(celsius):
return (celsius * 9/5) + 32
# Call the function
f = celsius_to_fahrenheit(25)
print(f"25°C = {f:.1f}°F") # Output: 25°C = 77.0°F
Example 2: Function with multiple parameters and default values
def create_profile(name, age, city="Unknown"):
return f"{name}, {age} years old, from {city}"
print(create_profile("Alice", 30))
# Output: Alice, 30 years old, from Unknown
print(create_profile("Bob", 25, "New York"))
# Output: Bob, 25 years old, from New York
Named arguments let you skip order:
print(create_profile(age=22, name="Charlie", city="London"))
# Output: Charlie, 22 years old, from London
Example 3: Returning multiple values
Use a tuple (you can unpack it on the caller side):
def analyze_numbers(a, b):
total = a + b
product = a * b
return total, product # returns a tuple
sum_result, prod_result = analyze_numbers(4, 7)
print(f"Sum: {sum_result}, Product: {prod_result}")
# Output: Sum: 11, Product: 28
Example 4: Documenting your function
def calculate_bmi(weight_kg, height_m):
"""
Calculate Body Mass Index.
Parameters:
weight_kg (float): Weight in kilograms.
height_m (float): Height in meters.
Returns:
float: BMI value rounded to 1 decimal.
"""
bmi = weight_kg / (height_m ** 2)
return round(bmi, 1)
Calling help(calculate_bmi) prints that docstring.
Compare options / when to choose what
| Technique | Best For | Returns | Caution |
|---|---|---|---|
Function with return |
Computing and returning a value | The value | Don't forget return |
Function without return |
Performing an action (side effect) | None |
Can't use result in expressions |
| Function with default params | Common values that rarely change | As defined | Mutable default args cause bugs |
| Named arguments | Functions with many optional params | As defined | Keyword order is irrelevant |
| Returning multiple values | Needing more than one result from one computation | Tuple | Unpack with matching variable count |
Pro tip: Use a function with
returnwhen you need the output for further calculations. Use a void function (noreturn) when the function's job is to print, save to a file, or modify a mutable object.
Troubleshooting & edge cases
Mistake 1: Defining a function but never calling it
def add(x, y):
return x + y
# No output — the function is just defined, never executed
Fix: Add print(add(2,3)) or result = add(2,3) somewhere.
Mistake 2: Forgetting the colon or indentation
def greet(name) # SyntaxError: expected ':'
print("Hello!") # IndentationError: unexpected indent
Fix: Always end the def line with :, and ensure the body is indented exactly 4 spaces (or one tab).
Mistake 3: Using mutable default arguments
def append_to_list(item, my_list=[]): # Dangerous! Shared across calls
my_list.append(item)
return my_list
print(append_to_list(1)) # [1]
print(append_to_list(2)) # [1, 2] — not [2]!
Fix: Use None and create a new list inside:
def append_to_list(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list
Mistake 4: Passing wrong argument types
def divide(a, b):
return a / b
divide(10, "2") # TypeError: unsupported operand type(s)
Fix: Validate input with isinstance() or rely on duck typing with clear documentation.
What you learned & what's next
You now know the core ideas behind define and call your own functions: how def works, parameters vs arguments, return values, default values, and common pitfalls. This is the foundation for writing modular, reusable code.
Next up: you'll learn how functions interact with data structures — specifically passing lists and dictionaries to functions, and how modifications inside a function affect the original object. That topic builds directly on today's lesson.
Practice recap
Write a function called factorial(n) that returns the factorial of a non-negative integer n (using a loop or recursion). Then write a second function combinations(n, k) that uses your factorial function to compute n! / (k! * (n-k)!). Test both with small values and verify against known results.
Common mistakes
- Forgetting to call the function (writing
my_functionwithout parentheses prints the function object, never executes it). - Using a mutable default argument like
def add_item(item, lst=[])— the list object is shared across calls, causing unexpected accumulation. - Defining a function after you try to call it — Python executes top-down, so a function must be defined before its first use in the script.
- Omitting the
returnstatement and trying to use the function's result in an expression — the function returnsNone, which often leads toTypeError: unsupported operand type(s). - Mixing positional and keyword arguments incorrectly — positional arguments must come before keyword arguments in a function call.
Variations
- Lambda (anonymous functions) for short, single-expression functions:
lambda x, y: x + y. - Nested functions defined inside another function (closures) to capture enclosing scope.
- Using
*argsand**kwargsto accept variable numbers of positional and keyword arguments in a single function definition.
Real-world use cases
- Encapsulate a discount calculation in an e‑commerce checkout system — call the function with purchase amount and discount code to get the final price.
- Build a reusable validation function that checks user input (e.g., email format or password strength) and returns
True/Falsewith an error message. - Create a data-cleaning pipeline where each step is a separate function (remove nulls, normalize dates, encode categories) — compose them together in a master function.
Key takeaways
- A function is defined with
defkeyword, a name, parentheses for parameters, a colon, and an indented body. - Call a function by writing its name followed by parentheses, with arguments inside matching the parameters' positions or names.
- Use
returnto send a value back to the caller; without it, the function returnsNone. - Default parameter values are evaluated once at function definition time — avoid mutable defaults like
[]or{}. - Keyword arguments let you pass arguments in any order and make function calls more readable.
- Always document your function with a docstring (
"""...""") to explain purpose, parameters, and return value.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.