Functions
Validate user email formats. Calculate shipping costs. Send welcome emails. Parse JSON responses. Format database queries. Hash passwords. Convert temperatures. Every one of these tasks gets repeated throughout your codebase—sometimes hundreds of times.
Functions let you write the logic once, name it, and reuse it everywhere. They're the fundamental building block of code organization, enabling you to break complex programs into manageable, testable, reusable pieces. Master functions, and you master abstraction—one of the core principles of programming.
What is a Function?
A function is a named, reusable block of code that performs a specific task. You define it once with def, then call it as many times as needed:
| Basic Function | |
|---|---|
- Function definition:
defkeyword, name (add_numbers), parameters with type hints (a: int, b: int), return type annotation (-> int), colon - The
returnstatement sends a value back to the caller—this function returns the sum - Function call: use the function name with parentheses and arguments—the function executes and returns
8
Function anatomy:
defkeyword: Declares the start of a function definition- Function name: Identifies the function (use descriptive names:
calculate_tax, notct) - Parameters: Inputs the function accepts (can be zero or more)
- Type hints (optional): Document expected types—
a: intmeansashould be an integer,-> intmeans it returns an integer - Colon: Marks the end of the definition line
- Indented body: The code that runs when the function is called
returnstatement: Sends a value back to the caller (optional—functions withoutreturnimplicitly returnNone)
Why Functions Matter
Functions are how programmers manage complexity. They enable:
- Code reuse: Write once, use everywhere—email validation, tax calculation, data formatting all become one-liners
- Maintainability: Fix a bug in one place, and every call gets the fix—no hunting through thousands of lines for copy-pasted code
- Testability: Test a function in isolation with known inputs and expected outputs—much easier than testing entire programs
- Abstraction: Hide complex logic behind simple names—
send_email()is clearer than 50 lines of SMTP protocol code - Organization: Break 1000-line scripts into 20 well-named functions—each does one thing well
- Collaboration: Teams can work on different functions simultaneously—clear interfaces between components
Without functions, every program would be a linear script. Functions transform code from a sequence of instructions into a collection of reusable tools.
DRY: Don't Repeat Yourself
Send the same welcome message to multiple users. Apply the same validation to multiple form fields. Calculate the same metric for different datasets. Repetition is code smell—functions eliminate it:
| Repetitive Code (Don't Do This) | |
|---|---|
- Same string concatenation repeated three times—error-prone and hard to change
| DRY with Functions | |
|---|---|
- Define the logic once—now changing the greeting message requires editing one line, not three (or hundreds)
- Combine with a for loop to process any number of users—scales from 3 to 3000
Functions with Loops
Convert multiple temperatures. Process a batch of files. Validate a list of email addresses—apply the same function to different inputs by combining functions with for loops:
| Applying Functions to Multiple Inputs | |
|---|---|
- Define the conversion logic once—this function handles any single temperature
- Loop over the list and apply the function to each element—output: "9.1°C = 282.25K", "8.8°C = 281.95K", "-270.15°C = 3.0K"
This pattern—define function, apply to collection—is fundamental to data processing. You could process 3 temperatures or 3 million with the same code.
Default Parameter Values
Greeting messages with customizable text. API calls with optional parameters. Configuration functions with sensible defaults—default values make parameters optional:
| Default Parameters | |
|---|---|
- Parameters with
=have defaults—greetingdefaults to "Hello",punctuationto "!" if not provided - Uses both defaults: "Hello, Alice!"
- Overrides first default but uses second: "Hi, Bob!"
Mutable Default Arguments Trap
Never use mutable objects (lists, dicts) as default values—they're shared across all calls and cause subtle bugs!
| The Bug | |
|---|---|
- The empty list is created once when the function is defined, not each time it's called—all calls share the same list!
| The Fix | |
|---|---|
- Use
Noneas the sentinel value for "no list provided" - Create a new list inside the function—each call gets its own list
Keyword Arguments
Create users with optional flags. Configure functions with many parameters. Make calls self-documenting—keyword arguments let you specify parameters by name:
- Function has 2 required parameters and 2 optional parameters with defaults
- Specify
is_activeby name, skipis_admin(uses defaultFalse)—keyword arguments make the call self-documenting
*args: Variable Positional Arguments
Sum any number of values. Concatenate multiple strings. Find the maximum of an unknown quantity of numbers—*args accepts any number of positional arguments:
| Variable Positional Arguments | |
|---|---|
*numberscollects all positional arguments into a tuple namednumbers—you can pass 0, 1, or 100 arguments- Four arguments →
numbersbecomes(10, 20, 30, 40) - Zero arguments →
numbersbecomes()(empty tuple)
The name args is convention—you could use *values or *items. The asterisk (*) is what triggers the behavior.
| Mixing Required and *args | |
|---|---|
greetingis required,*namescollects all remaining arguments—must have at least one argument (the greeting)- "Hello" fills
greeting, the rest go intonamestuple
**kwargs: Variable Keyword Arguments
Configuration functions. Building dictionaries from arguments. Flexible API wrappers—**kwargs accepts any number of keyword arguments:
| Variable Keyword Arguments | |
|---|---|
**kwargscollects all keyword arguments into a dictionary namedkwargs- Three keyword arguments →
kwargsbecomes{"name": "Alice", "age": 30, "city": "New York"}
The Full Parameter Order
When mixing parameter types, they must appear in this strict order:
- Regular positional arguments—must come first
- Parameters with defaults—after required positionals
*argsfor variable positional—collects extras into tuple- Keyword-only parameters (after
*args)—can only be set by name **kwargsfor variable keyword—always last, collects extras into dict
Common Patterns in Practice
You rarely need all five. The most common combinations:
def f(a, b, c=None)— required with optional defaultsdef f(*args)— variable number of same-type items (sum, max, concatenate)def f(**kwargs)— configuration functions, flexible APIsdef f(*args, **kwargs)— wrapper functions that forward everything to another function
Returning Multiple Values
Get min and max in one call. Return status code and message. Calculate multiple statistics—functions can return multiple values via tuple packing:
- Type hint shows function returns a tuple of two integers
- Comma-separated values create a tuple—
return min(numbers), max(numbers)is equivalent toreturn (min(numbers), max(numbers)) - Tuple unpacking assigns each returned value to a variable
| Returning Structured Data | |
|---|---|
- Returning a dictionary provides named access to multiple values—more self-documenting than tuples for complex data
Docstrings
Document your functions. Explain parameters and return values. Provide usage examples—docstrings are how Python functions document themselves:
| Basic Docstring | |
|---|---|
- Triple-quoted string immediately after function definition—this is the docstring
For complex functions, use multi-line docstrings with sections:
- Brief summary on first line
- Args section documents each parameter
- Returns section describes the return value
- Raises section lists exceptions the function can raise
- Example section shows usage with expected output (doctest format)
Accessing Docstrings Programmatically
Access docstrings with __doc__ attribute or help() function:
Lambda Functions
Sort by custom criteria. Transform data inline. Quick callback functions—lambdas are anonymous, single-expression functions for short operations:
| Lambda Syntax | |
|---|---|
- Lambda syntax:
lambda parameters: expression—nodef, noreturn, single expression only
Where Lambdas Shine
Lambdas excel as arguments to functions like sorted(), map(), filter():
- The
keyparameter takes a function that extracts the sort value—lambda provides an inline function without needingdef
map()applies the lambda to each element—transforms [1, 2, 3] → [1, 4, 9]filter()keeps only elements where the lambda returnsTrue- List comprehensions are often more Pythonic than
map()/filter()with lambdas
Lambda Limitations
Lambdas are limited to a single expression—no statements, no assignments, no multiple lines. If you need if/else logic beyond a ternary expression, multiple operations, or better debugging, use a regular def function. PEP 8: "Readability counts."
Variable Scope
Which variables can this function see? Can I modify that global counter? What happens when nested functions use the same name? Scope rules determine where variables are accessible:
Local Scope
Variables defined inside a function exist only within that function:
| Local Scope | |
|---|---|
messageonly exists insidemy_function—created when function runs, destroyed when it returns- Would raise
NameError: name 'message' is not defined—messagedoesn't exist outside the function
Global Scope
Variables defined at module level are accessible everywhere:
| Global Scope | |
|---|---|
greetingis global—defined outside any function- Functions can read global variables—
greetingis accessible here
Modifying Global Variables
To modify a global variable inside a function, explicitly declare it with global:
| The global Keyword | |
|---|---|
global countertells Python "I want to modify the globalcounter, not create a new local one"—without this,counter += 1would raiseUnboundLocalError
Global Variables Are Code Smell
Modifying global variables makes code hard to test and reason about—functions have hidden dependencies. Prefer returning values and passing parameters. If you're using global frequently, refactor to classes or pass state explicitly.
The nonlocal Keyword
For nested functions, nonlocal modifies variables from enclosing (non-global) scopes:
| nonlocal for Nested Functions | |
|---|---|
countis in the enclosing scope—not global, not local toinnernonlocal countallowsinnerto modifyouter'scountvariable
Scope Lookup Order (LEGB Rule)
Python searches for names in this order:
- Local—inside the current function
- Enclosing—in enclosing functions (for nested functions)
- Global—at the module level
- Built-in—Python's built-in names (
print,len, etc.)
| LEGB in Action | |
|---|---|
- Each function can have its own
xvariable—they don't conflict - Python finds the innermost (most local)
xfirst—prints "local", not "enclosing" or "global"
Type Hints
Document expected types. Enable IDE autocompletion. Catch type errors before runtime—type hints make Python code more maintainable:
| Type Hints Basics | |
|---|---|
name: strmeans parameter expects a string,-> strmeans function returns a string- Generic types like
list[str]specify the type of elements—list of strings → dictionary of string keys to integer values
Type hints are optional and don't affect runtime—Python won't raise errors for wrong types. They're primarily for:
- Documentation: Makes function signatures self-documenting
- IDE support: Enables autocompletion and inline error checking
- Static analysis: Tools like
mypycatch type errors before runtime - Refactoring: Easier to understand what changes might break
| Optional and Union Types | |
|---|---|
Optional[dict]means "either a dict or None"—shorthand fordict | Noneint | strmeans "either an int or a str" (Python 3.10+ syntax, cleaner thanUnion[int, str])
Key Takeaways
| Concept | What to Remember |
|---|---|
| Default parameters | def f(x, y=10) — defaults come after required |
| Keyword arguments | f(y=5, x=3) — specify by name |
| *args | Collect extra positional args into a tuple |
| **kwargs | Collect extra keyword args into a dict |
| Multiple returns | return a, b — returns a tuple |
| Docstrings | """Documentation""" right after def |
| Lambda | lambda x: x * 2 — anonymous single-expression function |
| Local scope | Variables inside function aren't visible outside |
| global | Declare intent to modify a global variable |
| nonlocal | Modify variable from enclosing function scope |
| Type hints | def f(x: int) -> str: — for documentation and tools |
Practice Problems
Practice Problem 1: Default Parameters
What will this code print?
Practice Problem 2: *args
What's the difference between these two functions?
Answer
sum_list()takes a single argument (a list):sum_list([1, 2, 3])sum_args()takes variable positional arguments:sum_args(1, 2, 3)
Both work internally the same way (summing a sequence), but the calling interface differs. *args collects multiple arguments into a tuple.
Practice Problem 3: Scope
What does this code print?
Practice Problem 4: Lambda Functions
Rewrite this using a lambda function:
Further Reading
- Python Functions Tutorial - Official documentation on functions
- PEP 8 – Style Guide - Function naming conventions and best practices
- PEP 257 – Docstring Conventions - How to write good docstrings
- PEP 484 – Type Hints - Introduction to type hints in Python
- PEP 3107 – Function Annotations - The proposal that introduced function annotations
- Computational Thinking: Abstraction - Understanding functions as abstraction mechanisms
Functions are the building blocks of organized, reusable code. They transform repetitive tasks into single, named operations. They enable testing, collaboration, and maintainability. Every complex program is built from simple functions composed together.
Master the fundamentals: parameters, return values, scope. Understand the advanced features: *args, **kwargs, decorators, closures. Write clear docstrings, use type hints, follow PEP 8 naming conventions. A well-written function does one thing, does it well, and has a name that clearly communicates its purpose.
The difference between a script and a program is organization. Functions are how you organize code.