Functions

Learn everything about functions in Python, including how to define, call, and use different types of arguments like default, keyword, required, and variable-length.

Loading...
Functions

What Is a Function?

A function is a reusable block of code that performs a specific task.

For example, if you often need to print a greeting, you could put that code inside a function.

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

Now you can use that function whenever you want:

greet()
greet()
greet()
 
# Output:
 
# Hello!
# Hello!
# Hello!

Instead of writing the same print() statement three times, we wrote it once and reused it.

Creating a Function

We use the def keyword to create a function.

The basic syntax is:

def function_name():
    # code to run

For example:

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

Here:

  • def tells Python that we’re creating a function.
  • greet is the name of the function.
  • () comes after the function name.

The indented code is the function body.

Calling a Function

Creating a function doesn’t run the code inside it.

To run the function, you need to call it.

def greet():
    print("Hello!")
 
greet()
 
# Output:
 
# Hello!

The line: greet() is what actually runs the code inside the function.

Think of it like this:

  • Define the function → create it
  • Call the function → run it

You can call the same function multiple times:

def greet():
    print("Hello!")
 
greet()
greet()
greet()
 
# Output:
 
# Hello!
# Hello!
# Hello!

What Does a Function Return?

So far, our greet() function only prints a message:

def greet():
    print("Hello!")
 
result = greet()
 
print(result)
 
# Output:
 
# Hello!
# None

Why does result contain None?

Because greet() doesn’t explicitly return a value.

Remember print()? print() also returns None.

The same thing happens with a function that doesn’t have a return statement.

We’ll learn how to make a function return a real value later in this lesson.

Function Names

Just like variables, function names should be descriptive and follow Python’s naming rules.

For example:

def say_hello():
    print("Hello!")
 
def show_profile():
    print("Profile")
 
def calculate_total():
    print("Calculating...")

Using a name that describes what the function does makes your code easier to understand.

Functions We’ve Already Used

We’ve actually been using functions in the earlier lessons as well.

For example:

print("Hello")

Here, print() is a Python built-in function.

We’ve also used:

input("Enter your name: ")
 
len("Python")
 
int("10")

These are all built-in functions provided by Python.

We don’t have to create them ourselves.

Now, we’re learning how to create our own user-defined functions.

Built-in vs User-Defined Functions

There are two basic types of functions you’ll come across:

Built-in Functions

These are already provided by Python.

Examples:

print()
input()
len()
int()
float()

User-Defined Functions

These are functions that you create yourself using def.

For example:

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

This is a user-defined function.

A Function Can Contain More Than One Line

A function can contain multiple lines of code.

For example:

def show_profile():
    print("--- Profile ---")
    print("Name: Shefali")
    print("City: Delhi")

Call it:

show_profile()
 
# Output:
 
# --- Profile ---
# Name: Shefali
# City: Delhi

All the indented lines belong to the function.

This is another reason functions are useful: you can group related code together.

Indentation Matters

Just like with if statements and loops, indentation tells Python which code belongs to the function.

# Correct:
def greet():
    print("Hello!")
 
# Incorrect:
def greet():
print("Hello!")

The second example causes an indentation error because the function body isn’t indented.

Why Use Functions?

Functions help you:

  • Reuse code
  • Avoid repeating the same code
  • Break a large program into smaller pieces
  • Keep your code organized
  • Make your code easier to read and maintain
  • As your programs get bigger, these benefits become more important.

What Is a Parameter?

A parameter is a variable that you define inside the parentheses when creating a function.

For example:

def greet(name):
    print("Hello", name)

Here, name is a parameter.

It gives the function a way to receive information.

Now we can call the function with different names:

greet("Shefali")
greet("John")
 
# Output:
 
# Hello Shefali
# Hello John

The function stays the same, but the information it works with changes.

What Is an Argument?

An argument is the actual value you pass to a function when you call it.

In this example:

greet("Shefali")

"Shefali" is the argument.

So:

  • def greet(name):, name → parameter
  • greet("Shefali"), "Shefali" → argument

A simple way to remember it:

  • Parameter: The variable in the function definition.
  • Argument: The value you pass when calling the function.

A Function With Multiple Parameters

A function can have more than one parameter.

For example:

def greet(first_name, city):
    print("Hello", first_name)
    print("You are from", city)

Now we can provide both values:

greet("Shefali", "Delhi")
 
# Output:
 
# Hello Shefali
# You are from Delhi

Here:

  • first_name is the first parameter.
  • city is the second parameter.
  • "Shefali" is the first argument.
  • "Delhi" is the second argument.

Positional Arguments

When you pass arguments normally, Python matches them to the parameters based on their position.

For example:

def greet(first_name, city):
    print("Hello", first_name)
    print("You are from", city)
 
greet("Shefali", "Delhi")

Python matches them like this:

  • first_name → “Shefali”
  • city → “Delhi”

The first argument goes to the first parameter and the second argument goes to the second parameter.

That’s why these are called positional arguments.

Order Matters

Because Python matches positional arguments by position, the order matters.

For example:

def introduce(name, age):
    print("Name:", name)
    print("Age:", age)
 
introduce("Shefali", 27)
 
# Output:
 
# Name: Shefali
# Age: 27

But if we change the order: introduce(27, "Shefali")

Python doesn’t know that we meant to put the name first.

It simply follows the positions:

  • name → 27
  • age → “Shefali”

The function receives the values in the order we provide them.

Keyword Arguments

You can also pass arguments by naming the parameter directly.

These are called keyword arguments.

For example:

def introduce(name, age):
    print("Name:", name)
    print("Age:", age)
 
introduce(age=27, name="Shefali")
 
# Output:
 
# Name: Shefali
# Age: 27

Notice that we passed age first and name second.

This works because Python matches the values by parameter name instead of position.

So with keyword arguments, the order doesn’t matter.

This can be especially useful when a function has several parameters and you want to make it clear which value belongs to which parameter.

Mixing Positional and Keyword Arguments

You can also mix positional and keyword arguments:

introduce("Shefali", age=27)

The rule is simple: Positional arguments must come before keyword arguments.

The Number of Arguments Matters

If a function expects two arguments:

def greet(name, city):
    print("Hello", name)
    print("You are from", city)

you need to provide both: greet("Shefali", "Delhi")

If you provide only one: greet("Shefali")

Python raises a TypeError because the required argument is missing.

Similarly, providing too many arguments causes an error: greet("Shefali", "Delhi", "India")

The function only expects two arguments.

So, for now, remember: When using positional arguments, the number and order of the arguments should match the parameters in the function.

Parameters Can Be Different Data Types

Parameters aren’t limited to strings.

You can pass numbers too:

def add(a, b):
    print(a + b)
 
add(5, 3)
 
# Output:
 
# 8

You can also pass other values you’ve already learned about.

For example:

def show_age(age):
    print("Your age is", age)
 
show_age(27)
 
# Output:
 
# Your age is 27

The function simply receives the value and uses it.

Parameters Make Functions Reusable

This is one of the main reasons parameters are useful.

Without parameters, you might create separate functions:

def greet_shefali():
    print("Hello Shefali")
 
def greet_john():
    print("Hello John")

That’s unnecessary.

Instead, we can create one function:

def greet(name):
    print("Hello", name)

Then reuse it:

greet("Shefali")
greet("John")
greet("Mark")
 
# Output:
 
# Hello Shefali
# Hello John
# Hello Mark

One function can now work with different values.

What Are Default Arguments?

A default argument is a value you give to a parameter when you define a function.

For example:

def greet(name="Mark"):
    print("Hello", name)

Here, "Mark" is the default value for the name parameter.

If we call the function without an argument:

greet()
 
# Output:
 
# Hello Mark

Python uses the default value because we didn’t provide one.

Providing a Different Value

You can still provide your own value when calling the function.

def greet(name="Mark"):
    print("Hello", name)
 
greet("John")
 
# Output:
 
# Hello John

When you provide an argument, Python uses that value instead of the default.

So greet() uses Mark while greet("John")uses John.

Why Use Default Arguments?

Default arguments are useful when a parameter usually has a common value, but you still want to give the caller the option to change it.

For example:

def greet(name="there"):
    print("Hello", name)

Now both of these work:

greet()
greet("Shefali")
 
# Output:
 
# Hello there
# Hello Shefali

You don’t have to provide a value every time.

Multiple Default Arguments

A function can have more than one parameter with a default value.

For example:

def greet(name="there", message="Hello"):
    print(message, name)

Now:

greet()
 
# Output:
 
# Hello there

You can also provide different values:

greet("Shefali", "Welcome")
 
# Output:
 
# Welcome Shefali

You can also provide only the first value:

greet("John")
 
# Output:
 
# Hello John

Here, "John" replaces the default value for name, while "Hello" remains the default value for message.

Default Arguments and Positional Arguments

Default arguments become especially useful when a function has some parameters that are optional.

For example:

def introduce(name, city="Delhi"):
    print("Name:", name)
    print("City:", city)

The name parameter doesn’t have a default value, so we need to provide it.

But city has a default value.

This works:

introduce("Shefali")
 
# Output:
 
# Name: Shefali
# City: Delhi

And we can also provide a different city:

introduce("Shefali", "Jaipur")
 
# Output:
 
# Name: Shefali
# City: Jaipur

A Rule to Remember

When defining a function, parameters without default values should come before parameters with default values.

This works:

def greet(name, message="Hello"):
    print(message, name)

But this does not:

def greet(name="Shefali", message):
    print(message, name)

Python raises a SyntaxError because a parameter without a default value comes after a parameter with a default value.

For now, the simple rule to remember is: Required parameters first, default parameters after them.

Default Values Can Be Different Types

A default value doesn’t have to be a string.

It can be a number too:

def show_age(age=18):
    print("Age:", age)
 
show_age()
show_age(27)
 
# Output:
 
# Age: 18
# Age: 27

You can also use other values you’ve already learned about.

Default Arguments vs Required Arguments

Compare these two functions:

def greet(name):
    print("Hello", name)

Here, name is required.

You need to provide it: greet("Shefali")

Now compare:

def greet(name="there"):
    print("Hello", name)

Here, name has a default value.

You can call the function without providing it: greet()

The main difference is whether the parameter already has a value to use when no argument is provided.

Now, we saw above that a function without a return statement gives back None.

So, let’s learn how to make a function return a value.

What Does return Do?

The return statement sends a value back from a function.

For example:

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

When we call the function:

result = add(5, 3)
 
print(result)
 
# Output:
 
# 8

The function calculates 5 + 3 and returns the result.

That returned value is then stored in result.

You can think of it like this:

Function → does some work → returns a value.

return vs print()

This is an important difference.

Consider this function:

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

If we call it:

result = add(5, 3)

It prints 8.

But result contains None.

Why?

Because print() displays a value. It doesn’t return that value from the function.

Now compare it with:

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

When we do:

result = add(5, 3)

result contains 8.

So: print() displays a value.

return gives a value back from a function.

Using the Returned Value

One of the useful things about return is that you can store the returned value and use it later.

def add(a, b):
    return a + b
 
result = add(5, 3)
 
print(result)
print(result * 2)
 
# Output:
 
# 8
# 16

The function returns 8. We store it in result and then we can use result just like any other value.

Returning Different Values

A function can return different values depending on a condition.

For example:

def check_age(age):
    if age >= 18:
        return "Adult"
    else:
        return "Minor"

Now:

result = check_age(20)
 
print(result)
 
# Output:
 
# Adult

And:

result = check_age(15)
 
print(result)
 
# Output:
 
# Minor

The function returns a different value depending on the condition.

Returning Multiple Values

A function can also return more than one value at once.

For example:

def get_name_and_age():
    return "Shefali", 27
 
name, age = get_name_and_age()
 
print(name)
print(age)
 
# Output:
 
# Shefali
# 27

Here, the function returns two values: return "Shefali", 27

Python allows us to assign those values directly to two variables: name, age = get_name_and_age()

This is similar to the multiple variable assignment we saw in the variable lesson.

Python actually groups the returned values together as a tuple. We’ll learn more about tuples later.

For now, just remember that you can return multiple values and assign them to separate variables.

return Stops the Function

When Python reaches a return statement, the function immediately stops running.

For example:

def greet():
    print("Hello")
    return
    print("This won't run")
 
greet()
 
# Output:
 
# Hello

The second print() never runs because return ended the function.

You can also return a value:

def greet():
    print("Hello")
    return "Done"
 
    print("This won't run")

Once Python reaches return "Done", the function stops.

Code placed after a return statement is called unreachable code.

Python won’t raise an error for it, but most code editors will warn you about it because that code can never run.

Returning More Complex Values

A function doesn’t have to return just numbers.

It can return strings and other values you’ve already learned about.

For example:

def get_name():
    return "Shefali"
 
name = get_name()
 
print(name)
 
# Output:
 
# Shefali

You can also return a result from a calculation:

def square(number):
    return number * number
 
result = square(5)
 
print(result)
 
# Output:
 
# 25

Returning Early

You can also use return to stop a function early.

For example:

def check_number(number):
    if number < 0:
        return "Negative number"
 
    return "Positive number"

If we call:

print(check_number(-5))
 
# Output:
 
# Negative number

When number < 0 is True, the first return runs and the function ends.

The second return isn’t reached.

One Function Can Return a Value to Another Part of Your Program

This is where functions become really useful.

For example:

def calculate_total(price, quantity):
    return price * quantity
 
total = calculate_total(100, 3)
 
print("Total:", total)
 
# Output:
 
# Total: 300

The function handles the calculation and gives the result back to the rest of the program.

The function doesn’t need to know what you’ll do with the result.

You can print it, store it, or use it in another calculation.

So far, we’ve learned how to pass arguments to functions.

But what if you don’t know how many arguments you’ll need?

For example, what if you want a function that can accept any number of values?

That’s where *args comes in.

What is args?

*args allows a function to accept any number of positional arguments.

For example:

def add_numbers(*args):
    print(args)
 
add_numbers(10, 20, 30)
 
# Output:
 
# (10, 20, 30)

Notice that the values are displayed inside parentheses.

Python collects all the positional arguments into a tuple.

We’ll learn more about tuples later. For now, just remember: *args collects multiple positional arguments into one variable.

args Is a Tuple

As we just saw, *args collects the arguments into a tuple.

Remember from data types lesson, that tuples are immutable. That means you can read their values, but you can’t change them.

For example:

def change(*args):
    args[0] = 99
 
change(1, 2, 3)

This raises: TypeError: 'tuple' object does not support item assignment

Since *args is a tuple, you can read its values but not change them.

Why Do We Need args?

Normally, you need to define how many parameters a function accepts:

def add_numbers(a, b):
    print(a + b)
 
add_numbers(10, 20)

This function expects exactly two arguments.

If you try add_numbers(10, 20, 30), Python raises an error because the function only has two parameters.

With *args, you can accept as many positional arguments as you need:

def add_numbers(*args):
    print(args)
 
add_numbers(10, 20, 30, 40, 50)
 
# Output:
 
# (10, 20, 30, 40, 50)

args Is Just a Name

The name args is not special.

The * is what matters.

For example:

def add_numbers(*numbers):
    print(numbers)
 
add_numbers(10, 20, 30)
 
# Output:
 
# (10, 20, 30)

You could technically call it anything:

def add_numbers(*values):
    print(values)

But *args is the common convention, so you’ll see it often in Python code.

Looping Through args

Since the collected values can be worked with one by one, you can loop through them:

def show_numbers(*args):
    for number in args:
        print(number)
 
show_numbers(10, 20, 30)
 
# Output:
 
# 10
# 20
# 30

You can also use len():

def count_numbers(*args):
    print("You provided", len(args), "numbers.")
 
count_numbers(10, 20, 30, 40)
 
# Output:
 
# You provided 4 numbers.

This uses things you’ve already learned:

  • functions
  • arguments
  • for loops
  • len()

Using args to Add Numbers

Now let’s make our earlier example more useful:

def add_numbers(*args):
    total = 0
 
    for number in args:
        total = total + number
 
    return total
 
result = add_numbers(10, 20, 30)
 
print(result)
 
# Output:
 
# 60

The function can now accept any number of numbers:

print(add_numbers(5, 10))
print(add_numbers(5, 10, 15))
print(add_numbers(5, 10, 15, 20, 25))
 
# Output:
 
# 15
# 30
# 75

That’s the main benefit of *args.

You don’t have to decide in advance how many positional arguments the function should accept.

A Quick Note About sum()

Python already has a built-in sum() function that adds a collection of numbers.

For example:

numbers = (10, 20, 30)
print(sum(numbers))
 
# Output:
 
# 60

We wrote our own add_numbers() function above to understand how *args works.

In real code, if you simply need to add a collection of numbers, Python’s built-in sum() is usually the simpler choice.

args With Regular Parameters

You can also use regular parameters together with *args.

For example:

def greet(greeting, *names):
    for name in names:
        print(greeting, name)
 
greet("Hello", "Shefali", "John", "Mark")
 
# Output:
 
# Hello Shefali
# Hello John
# Hello Mark

Here:

  • greeting receives "Hello"
  • *names collects the remaining positional arguments

So names contains: ("Shefali", "John", "Mark")

One Important Rule

When you use a regular parameter before *args, the regular parameter gets its value first.

For example:

def show(first, *others):
    print("First:", first)
    print("Others:", others)
 
show(10, 20, 30, 40)
 
# Output:
 
# First: 10
# Others: (20, 30, 40)

The first argument goes to first and the remaining positional arguments are collected by *others.

args Doesn’t Mean “Any Type”

*args controls how many positional arguments a function accepts.

It doesn’t mean all the arguments have to be the same type.

For example:

def show_values(*args):
    print(args)
 
show_values(10, "Hello", 3.14)
 
# Output:
 
# (10, 'Hello', 3.14)

You can pass different types of values.

args vs Normal Parameters

Here’s the difference:

Normal parameters

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

The function expects two arguments.

With *args

def add(*args):
    total = 0
 
    for number in args:
        total = total + number
 
    return total

The function can accept any number of positional arguments.

add(1, 2)
add(1, 2, 3)
add(1, 2, 3, 4, 5)

What Happens If You Don’t Pass Anything?

You can call a *args function without providing any arguments:

def show_values(*args):
    print(args)
 
show_values()
 
# Output:
 
# ()

The function receives an empty tuple.

This can be useful when you want a function to accept zero or more values.

A Common Mistake

Don’t forget the *.

This:

def show_values(args):
    print(args)

is a normal parameter.

It expects one argument: show_values(10)

But this:

def show_values(*args):
    print(args)

can accept multiple positional arguments: show_values(10, 20, 30)

So the * changes how Python collects the arguments.

What is kwargs?

**kwargs allows a function to accept any number of keyword arguments.

For example:

def show_info(**kwargs):
    print(kwargs)
 
show_info(name="Shefali", age=27, city="Delhi")
 
# Output:
 
# {'name': 'Shefali', 'age': 27, 'city': 'Delhi'}

Python collects the keyword arguments into a dictionary.

We’ll learn more about dictionaries later. For now, remember: **kwargs collects multiple keyword arguments into one variable.

Why Do We Need kwargs?

Normally, you define the parameters a function expects:

def introduce(name, age):
    print("Name:", name)
    print("Age:", age)
 
introduce(name="Shefali", age=27)

This works well when you know exactly which parameters you need.

But what if you want your function to accept additional named values?

That’s where **kwargs can help:

def introduce(**kwargs):
    print(kwargs)
 
introduce(name="Shefali", age=27, city="Delhi")
 
# Output:
 
# {'name': 'Shefali', 'age': 27, 'city': 'Delhi'}

The function can accept any number of keyword arguments.

kwargs Is Just a Name

Just like args, the name kwargs is not special.

The ** is what matters.

You could write:

def show_info(**details):
    print(details)
 
show_info(name="Shefali", age=27)
 
# Output:
 
# {'name': 'Shefali', 'age': 27}

But **kwargs is the common convention you’ll usually see in Python code.

args vs kwargs

This is the main difference:

*args **kwargs
Accepts positional arguments Accepts keyword arguments
Collected into a tuple Collected into a dictionary
Uses one * Uses two **

For example:

def show(*args, **kwargs):
    print(args)
    print(kwargs)
 
show(10, 20, name="Shefali", age=27)
 
# Output:
 
# (10, 20)
# {'name': 'Shefali', 'age': 27}

The positional arguments go into args.

The keyword arguments go into kwargs.

Looping Through kwargs

Since kwargs is a dictionary, you can loop through it:

def show_info(**kwargs):
    for key, value in kwargs.items():
        print(key, ":", value)
 
show_info(name="Shefali", age=27, city="Delhi")
 
# Output:
 
# name : Shefali
# age : 27
# city : Delhi

Don’t worry too much about .items() yet. We’ll learn dictionaries properly later.

For now, the important idea is that kwargs contains named values.

Using kwargs With Regular Parameters

You can also use regular parameters together with **kwargs.

For example:

def greet(name, **details):
    print("Hello", name)
    print(details)
 
greet("Shefali", age=27, city="Delhi")
 
# Output:
 
# Hello Shefali
# {'age': 27, 'city': 'Delhi'}

Here:

  • name receives "Shefali"
  • **details collects the remaining keyword arguments

args and kwargs Together

You can use both in the same function.

def show_info(*args, **kwargs):
    print("Positional:", args)
    print("Keyword:", kwargs)
 
show_info("Python", "JavaScript", name="Shefali", age=27)
 
# Output:
 
# Positional: ('Python', 'JavaScript')
# Keyword: {'name': 'Shefali', 'age': 27}

This gives the function a lot of flexibility.

You can pass:

  • any number of positional arguments
  • any number of keyword arguments

The Order Matters

When using regular parameters, *args, and **kwargs together, they follow a specific order.

For example:

def example(name, *args, **kwargs):
    print(name)
    print(args)
    print(kwargs)
 
example(
    "Shefali",
    "Python",
    "JavaScript",
    age=27,
    city="Delhi"
)
 
# Output:
 
# Shefali
# ('Python', 'JavaScript')
# {'age': 27, 'city': 'Delhi'}

The general order is:

regular parameters → *args → **kwargs

You don’t need to memorize every rule around function parameters yet. Just remember this order when you see them together.

Keyword Arguments Need Names

With **kwargs, you pass values using names:

show_info(name="Shefali", age=27)

This is different from *args:

show_info("Shefali", 27)

The first version uses keyword arguments.

The second version uses positional arguments.

That’s why Python can separate them when both *args and **kwargs are used.

A Practical Example

Imagine you’re creating a function to display information about a user.

You don’t know which pieces of information the caller wants to provide.

You could use:

def user_profile(**details):
    for key, value in details.items():
        print(key, ":", value)
 
user_profile(
    name="Shefali",
    age=27,
    city="Delhi"
)
 
# Output:
 
# name : Shefali
# age : 27
# city : Delhi

Another user could provide different information:

user_profile(
    name="John",
    country="India"
)

The same function can handle both.

That’s one reason **kwargs is useful.

A Common Mistake

Don’t confuse *args and **kwargs.

This:

def show(*args):
    print(args)

expects positional arguments: show(10, 20, 30)

While this:

def show(**kwargs):
    print(kwargs)
 
expects keyword arguments: show(name="Shefali", age=27)

The number of * characters matters.

  • *args → positional arguments
  • **kwargs → keyword arguments

What Happens If You Don’t Pass Anything?

Just like *args, you can call a **kwargs function without providing any arguments:

def show_info(**kwargs):
    print(kwargs)
 
show_info()
 
# Output:
 
# {}

You get an empty dictionary.

What Is pass?

Sometimes you want to create a function but don’t have any code to put inside it yet.

For example:

def add(a, b):

This won’t work because Python expects an indented code block after the function definition.

You can use pass as a placeholder:

def add(a, b):
    pass

The function is now valid Python code, but it doesn’t do anything yet.

You can still call it:

def add(a, b):
    pass
 
result = add(5, 3)
 
print(result)
 
# Output:
 
# None

Since the function doesn’t return a value, it returns None, just like the functions we saw earlier.

When Would You Use pass?

pass is useful when you want to create the structure of your code now and fill in the implementation later.

For example:

def calculate_total():
    pass
 
def display_result():
    pass

You can come back and write the actual code when you’re ready.

Think of pass as saying: “Do nothing here for now.”

What Is Recursion?

Now let’s look at a more interesting concept.

A recursive function is a function that calls itself.

For example:

def countdown(number):
    if number == 0:
        return
 
    print(number)
    countdown(number - 1)
 
countdown(5)
 
# Output:
 
# 5
# 4
# 3
# 2
# 1

Let’s understand what happens.

The function starts with: countdown(5)

It prints 5, then calls itself with 4: countdown(4)

Then:

countdown(3)
countdown(2)
countdown(1)

Eventually, it reaches: countdown(0)

At that point, this condition is true:

if number == 0:
    return

The function stops.

The Base Case

A recursive function needs a condition that tells it when to stop.

This is called the base case.

In our example:

if number == 0:
    return

is the base case.

Without it, the function would keep calling itself.

For example, this is dangerous:

def countdown(number):
    print(number)
    countdown(number - 1)

There is nothing telling the function to stop.

Eventually, Python will raise a: RecursionError

So remember: A recursive function needs a base case to stop the recursion.

A Practical Example: Factorial

Recursion can also be used to solve mathematical problems.

One common example is factorial.

The factorial of 5 is: 5 × 4 × 3 × 2 × 1 = 120

We can write a recursive function for this:

def factorial(number):
    if number == 0:
        return 1
 
    return number * factorial(number - 1)
 
print(factorial(5))
 
# Output:
 
# 120

Let’s see how this works:

factorial(5)
5 × factorial(4)
 
5 × 4 × factorial(3)
 
5 × 4 × 3 × factorial(2)
 
5 × 4 × 3 × 2 × factorial(1)
 
5 × 4 × 3 × 2 × 1

The final result is: 120

The important part is this: return number * factorial(number - 1)

The function calls itself with a smaller number each time.

Eventually factorial(0) is reached.

That’s our base case:

if number == 0:
    return 1

Recursion and return

Notice that recursion often works together with return.

In our factorial example:

return number * factorial(number - 1)

The function keeps calling itself until it reaches the base case.

Then the returned values are used to calculate the final result.

This is another example of why the return statement is so important.

Recursion vs Loops

You might notice that our countdown example could also be written using a loop:

number = 5
 
while number > 0:
    print(number)
    number = number - 1

So why use recursion?

Recursion can be useful when a problem naturally breaks down into smaller versions of the same problem.

You’ll see recursion used in areas like:

  • Working with trees
  • Searching through nested structures
  • Sorting algorithms
  • Some mathematical problems

But recursion isn’t always the best choice. Many problems can be solved more simply with a loop.

For now, the important thing is understanding how recursion works.

Python’s Recursion Limit

Python limits how deeply a function can call itself. By default, this is usually around 1,000 recursive calls.

Python also doesn’t optimize recursive calls in the way some other languages do.

That’s why, in Python, loops are often a better choice for simple repetition.

Recursion is more useful when a problem naturally breaks down into smaller versions of itself, like working with nested structures or certain algorithms.

A Common Mistake

The most common mistake with recursion is forgetting the base case.

This function never stops:

def count(number):
    print(number)
    count(number + 1)

There is no condition telling it when to stop.

A recursive function should always have a clear stopping condition.


👉 Next tutorial: Python Modules