Skip to main content
Python•Last Updated : 20 Oct 2026
~50 min read

Functions in Python

Defining and calling functions, parameters and return values, default and keyword arguments, scope, docstrings, functions as values, lambda, *args and **kwargs, and a first look at decorators.

Why functions

A function is a named block of code that does one job. You define it once with def and call it as often as you like by writing its name followed by parentheses.

Functions are how programs stay manageable:

  • No repetition: fix a bug in one place instead of five copies.
  • Names for ideas: is_valid_email(address) says what happens; ten lines of string checks do not.
  • Testable pieces: a function with clear inputs and outputs can be checked on its own.

The def line names the function and lists its parameters in parentheses, then ends with a colon. The indented body runs only when the function is called, not when it is defined.

Definition: Function

A reusable, named block of code. It receives input through parameters, runs its body when called, and hands a result back to the caller with return.

PythonDefine once, call many times
1def greet(name):
2 print(f"Hello, {name}!")
3
4greet("Ada")
5greet("Grace")
6print("greet is defined:", callable(greet))
Output
Hello, Ada!
Hello, Grace!
greet is defined: True

Parameters and arguments

Parameters are the names in the def line; arguments are the actual values passed in a call. When a function is called, each parameter is bound to its argument, and the body runs with those names.

Arguments can be passed by position (matched in order) or by keyword (name=value, matched by name). Keyword arguments make calls self-explanatory, especially for booleans and numbers: resize(image, 800, 600, keep_ratio=True) is clearer than four bare values. Positional arguments must come before keyword arguments in a call.

Calling with the wrong number of arguments raises a TypeError that names the missing or unexpected argument.

PythonPositional and keyword arguments
1def describe_pet(name, species, age):
2 print(f"{name} is a {age}-year-old {species}.")
3
4describe_pet("Milo", "cat", 3)
5describe_pet(age=5, name="Rex", species="dog")
6describe_pet("Kiwi", age=1, species="parrot")
Output
Milo is a 3-year-old cat.
Rex is a 5-year-old dog.
Kiwi is a 1-year-old parrot.

Returning a result

return ends the function and hands a value back to the caller, where it can be stored, printed or used in another expression. A function without a return (or with a bare return) gives back None.

Learn the difference between printing and returning early. print shows text to a person and the program cannot use it afterwards. return gives a value to the code that called the function. Functions that calculate something should return it and leave the printing to the caller.

To return several values, separate them with commas. Python packs them into a tuple, and the caller can unpack it directly.

Pythonreturn versus print
1def area_print(width, height):
2 print(width * height)
3
4def area(width, height):
5 return width * height
6
7result = area_print(3, 4)
8print("area_print returned", result)
9total = area(3, 4) + area(2, 5)
10print("Total area:", total)
11
12def min_max(values):
13 return min(values), max(values)
14
15low, high = min_max([7, 2, 9, 4])
16print(low, high)
Output
12
area_print returned None
Total area: 22
2 9

Default parameter values

A parameter can have a default value, used when the caller leaves that argument out: def greet(name, greeting="Hello"). Parameters with defaults must come after those without.

One trap is famous enough to know by heart: default values are evaluated once, when the function is defined, not on every call. A mutable default such as [] is therefore shared between calls and keeps growing. Use None as the default and create the list inside the function.

PythonDefaults, and the mutable-default trap
1def greet(name, greeting="Hello"):
2 return f"{greeting}, {name}!"
3
4print(greet("Ada"))
5print(greet("Ada", greeting="Welcome back"))
6
7def add_bad(item, basket=[]):
8 basket.append(item)
9 return basket
10
11print(add_bad("apple"))
12print(add_bad("pear"))
13
14def add_good(item, basket=None):
15 if basket is None:
16 basket = []
17 basket.append(item)
18 return basket
19
20print(add_good("apple"))
21print(add_good("pear"))
Output
Hello, Ada!
Welcome back, Ada!
['apple']
['apple', 'pear']
['apple']
['pear']

Scope: where a name is visible

Names created inside a function (its parameters and any variable it assigns) are local: they exist only while the function runs and are invisible outside it. Names defined at the top level of the file are global and can be read inside functions.

When Python looks up a name it searches in this order, remembered as LEGB: Local, then Enclosing functions, then Global, then Built-in (print, len, ...).

Assigning to a name inside a function makes it local to that function, even if a global with the same name exists. The global keyword can override this, but functions that change global variables are hard to reason about. Prefer passing values in as arguments and returning results.

Tip

A function that only uses its parameters and returns a value, without touching anything outside, is called pure. Pure functions are the easiest to test and reuse.

PythonLocal and global names
1rate = 0.2
2
3def tax(amount):
4 result = amount * rate
5 return result
6
7print(tax(150))
8print("rate" in globals(), "result" in globals())
9
10count = 10
11
12def reset():
13 count = 0
14 return count
15
16print(reset(), count)
Output
30.0
True False
0 10

Docstrings and designing good functions

The first statement of a function can be a string literal, its docstring. It documents what the function does, what it expects and what it returns, and tools such as help() and editors display it.

Good functions share a few traits:

  • One job, described by a verb-based name: parse_date, calculate_total, is_weekend.
  • Small: if you need "and" to describe it, consider splitting it.
  • Inputs in, output out: parameters and a return value rather than global variables, input() and print buried inside.
  • Guard clauses: reject invalid input at the top with an early return (or an exception), so the main logic is not nested inside conditions.
PythonA documented function with a guard clause
1def average(values):
2 """Return the mean of a list of numbers, or None for an empty list."""
3 if not values:
4 return None
5 return sum(values) / len(values)
6
7print(average([4, 8, 6]))
8print(average([]))
9print(average.__doc__)
Output
6.0
None
Return the mean of a list of numbers, or None for an empty list.

Functions are values

In Python a function is an object like any other: you can store it in a variable, put it in a list or dictionary, and pass it to another function. Functions that take other functions as arguments are called higher-order functions.

The most common case is the key argument of sorted, min and max: you pass a function that tells them what to compare. For tiny one-off functions, a lambda expression creates an unnamed function inline: lambda word: len(word). A lambda can only contain a single expression; anything longer deserves a def and a name.

map(f, items) applies f to every item, and filter(f, items) keeps the items for which f returns a truthy value. Both return lazy iterators, so wrap them in list() to see the results.

PythonPassing functions around
1def shout(text):
2 return text.upper() + "!"
3
4say = shout
5print(say("hi"))
6
7words = ["banana", "Kiwi", "apple", "fig"]
8print(sorted(words))
9print(sorted(words, key=str.lower))
10print(sorted(words, key=lambda w: (len(w), w.lower())))
11print(max(words, key=len))
12print(list(map(len, words)))
13print(list(filter(lambda w: len(w) > 3, words)))
14
15operations = {"double": lambda x: x * 2, "square": lambda x: x * x}
16print(operations["square"](7))
Output
HI!
['Kiwi', 'apple', 'banana', 'fig']
['apple', 'banana', 'fig', 'Kiwi']
['fig', 'Kiwi', 'apple', 'banana']
banana
[6, 4, 5, 3]
['banana', 'Kiwi', 'apple']
49

Flexible arguments: *args and **kwargs

Sometimes a function should accept any number of arguments, like print does. A parameter written *args collects all extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dictionary. The names args and kwargs are conventions; the stars are what matter.

The same stars work in the other direction in a call: f(*values) spreads a list into positional arguments and f(**options) spreads a dictionary into keyword arguments.

PythonCollecting and spreading arguments
1def total(*numbers):
2 return sum(numbers)
3
4print(total(1, 2, 3), total())
5
6def make_tag(name, **attributes):
7 parts = [name] + [f'{k}="{v}"' for k, v in attributes.items()]
8 return "<" + " ".join(parts) + ">"
9
10print(make_tag("img", src="cat.png", alt="A cat"))
11
12point = [3, 4]
13print(total(*point))
Output
6 0
<img src="cat.png" alt="A cat">
7

Functions that wrap functions

A function defined inside another function can use the outer function's variables even after the outer function has returned. Such an inner function is a closure. Closures make it possible to write functions that take a function and return an improved version of it.

That pattern is a decorator. Writing @log_call above a def is shorthand for name = log_call(name): the function is replaced by a wrapper that adds behaviour (logging, timing, checking permissions, caching) and then calls the original. functools.wraps copies the original's name and docstring onto the wrapper.

You will use decorators from libraries long before you write many of your own, so the goal here is to recognise what @something means.

PythonA small logging decorator
1import functools
2
3def log_call(func):
4 @functools.wraps(func)
5 def wrapper(*args, **kwargs):
6 result = func(*args, **kwargs)
7 print(f"{func.__name__}{args} -> {result}")
8 return result
9 return wrapper
10
11@log_call
12def add(a, b):
13 return a + b
14
15add(2, 3)
16add(10, -4)
17print(add.__name__)
Output
add(2, 3) -> 5
add(10, -4) -> 6
add

Check your understanding

Suggested Quiz
Question 1 of 5

What does `print(f(2))` show if `def f(x): x * 2` (no return)?

Practice

Decompose problems into small functions (a password checker and an ISBN validator), use higher-order functions, build a decorator, and design a calculator engine with explicit state.

[Problem]

Password Strength Checker

Reveal Solution & Analysis
Model Solution
[Problem]

ISBN-10 Validator

Reveal Solution & Analysis
Model Solution
[Problem]

Lambda, Map, Filter, Reduce

Reveal Solution & Analysis
Model Solution
[Problem]

Function Decorators: Call Logger

Reveal Solution & Analysis
Model Solution
[Problem]

Calculator Engine for a Tkinter GUI

Reveal Solution & Analysis
Model Solution

Quick Syntax & Reference Sheet

Concept / RuleSyntax / Usage
Define and calldef area(w, h): return w * h area(3, 4)
Default valuedef greet(name, greeting="Hello"):
Keyword argumentgreet("Ada", greeting="Hi")
Return several valuesreturn low, high
Docstring"""Return the mean of values."""
Lambda as a keysorted(people, key=lambda p: p[1])
Any number of argumentsdef f(*args, **kwargs):
Decorator@functools.wraps(func) inside a wrapper

Common Pitfalls & Developer Mistakes

✕ Mistake 1:

Printing instead of returning

Why it happens: The caller receives None and cannot use the result.

Correction: return the value and print it at the call site if needed.
✕ Mistake 2:

Forgetting the parentheses when calling

Why it happens: greet refers to the function object; greet() calls it.

Correction: Write greet() with parentheses (and arguments).
✕ Mistake 3:

Mutable default arguments

Why it happens: def f(items=[]) shares one list between calls.

Correction: def f(items=None): then items = [] if items is None else items.
✕ Mistake 4:

Changing a global inside a function without realising it creates a local

Why it happens: count = count + 1 inside a function raises UnboundLocalError.

Correction: Pass the value in and return the new one: count = increment(count).

Frequently Asked Questions

How long should a function be?

Long enough to do one clearly named job. If you cannot name it without "and", or it no longer fits on a screen, split it.

Can a function call itself?

Yes, that is recursion. It suits problems that contain smaller versions of themselves, such as nested dictionaries. Python limits recursion depth (around 1000 calls by default), so loops are preferred for long, simple repetition.

Are arguments passed by value or by reference?

Neither, exactly: the parameter is bound to the same object the caller passed. Rebinding the parameter has no effect outside, but mutating a mutable object (appending to a list) is visible to the caller.

When should I use lambda?

For short, single-expression functions passed directly to another function, such as a sort key. Give anything reused or more complex a proper def and a name.

Hands-On Practice Problems

Article Tags:#Python#Functions#Parameters#Scope#Lambda#Decorators
Was this lesson clear and helpful?

Discussion (0)

Sign in to ask questions and reply.
Loading discussion…
Discuss in community