How to create a function in Python

Learn how to create a function in Python. Explore different methods, tips, real-world applications, and how to debug common errors.

How to create a function in Python
Published on: 
Fri
Feb 6, 2026
Updated on: 
Mon
Apr 13, 2026
The Replit Team

Functions are essential in Python. They let you organize code into reusable blocks, which makes your programs more efficient and readable. This is a core concept for any Python developer.

In this article, we'll explore techniques to define functions, complete with practical tips. You'll also see real-world applications and get advice to debug your code for more robust programs.

Basic function definition with def

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

greet() # Call the function--OUTPUT--Hello, World!

The def keyword is Python's way of signaling a function definition. In this example, greet() is a function that takes no arguments, which is indicated by the empty parentheses. The indented block of code following the colon contains the logic that runs whenever you call the function.

By defining the function first, you create a reusable command. Calling greet() later executes that block of code. This separation is key to writing modular programs where you can run the same logic from multiple places without rewriting it.

Function parameters and return values

To make your functions more versatile than the simple greet() example, you'll need to pass data into them using parameters.

Using parameters to pass data to functions

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

result = add_numbers(5, 3)
print(f"The sum is: {result}")--OUTPUT--The sum is: 8

In the add_numbers function, a and b are parameters that act as placeholders for the data it will process. When you call the function with add_numbers(5, 3), you're passing arguments (the values 5 and 3) which get assigned to these parameters inside the function.

  • The return keyword is what makes the function output a value. It sends the result of a + b back to the line of code that called the function.
  • You can then capture this output in a variable—in this case, result—to use it elsewhere in your program.

Using default parameter values

def greet_person(name, greeting="Hello"):
return f"{greeting}, {name}!"

print(greet_person("Alice"))
print(greet_person("Bob", "Hi"))--OUTPUT--Hello, Alice!
Hi, Bob!

You can make function parameters optional by assigning them a default value using the = operator in the definition. In the greet_person function, the greeting parameter defaults to "Hello". This makes your functions more flexible, as you don't have to supply every argument on each call.

  • When you call greet_person("Alice"), the function falls back to the default value for greeting since it wasn't provided.
  • If you call greet_person("Bob", "Hi"), the value "Hi" overrides the default, giving you more control over the output.

Working with *args and **kwargs

def process_data(*args, **kwargs):
print(f"Positional arguments: {args}")
print(f"Keyword arguments: {kwargs}")

process_data(1, 2, 3, name="Alice", age=30)--OUTPUT--Positional arguments: (1, 2, 3)
Keyword arguments: {'name': 'Alice', 'age': 30}

Sometimes you need a function that can handle a variable number of arguments. That's where *args and **kwargs come in, giving your functions powerful flexibility.

  • The *args parameter collects any number of positional arguments into a tuple. In the process_data example, the values 1, 2, 3 are packed into the args tuple.
  • Similarly, **kwargs gathers any number of keyword arguments into a dictionary. The arguments name="Alice" and age=30 are collected into the kwargs dictionary.

This pattern allows you to create adaptable functions that don't require a predefined number of parameters.

Advanced function techniques

Once you're comfortable with defining functions, you can unlock more expressive and efficient patterns with anonymous lambda functions, decorators, and recursion.

Creating anonymous functions with lambda

square = lambda x: x ** 2
numbers = [1, 2, 3, 4, 5]
squared = list(map(square, numbers))
print(squared)--OUTPUT--[1, 4, 9, 16, 25]

A lambda function is a small, one-line anonymous function defined without a name using the lambda keyword. It's perfect for when you need a simple function for a short period. For more details on using lambda in Python, the example square = lambda x: x ** 2 creates a function that takes an argument x and returns its square.

  • The built-in map() function then applies this lambda function to every element in the numbers list, creating a new list of the results.
  • This approach is more concise than defining a full function with def, especially for simple operations.

Implementing function decorators

def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper

@my_decorator
def say_hello():
print("Hello!")

say_hello()--OUTPUT--Before function call
Hello!
After function call

Decorators are functions that modify or enhance other functions without changing their source code. They're a powerful tool for adding common functionalities like logging or timing. The @my_decorator syntax is a clean way to apply a decorator to a function like say_hello.

  • The decorator, my_decorator, defines and returns an inner wrapper function.
  • This wrapper executes extra code before and after running the original function, which is passed in as the func argument.

Creating recursive functions

def factorial(n):
if n <= 1:
return 1
else:
return n * factorial(n-1)

print(f"5! = {factorial(5)}")--OUTPUT--5! = 120

A recursive function solves a problem by calling itself until a specific condition is met. For a comprehensive guide to recursion in Python, the factorial example shows how the function repeatedly calls itself with a decremented argument (n-1). This process continues until it reaches the base case, which is the condition that stops the recursion.

  • The base case, if n <= 1, is crucial. It provides a stopping point to prevent an infinite loop.
  • The recursive step, return n * factorial(n-1), is where the function calls itself, breaking the problem into smaller pieces.

Move faster with Replit

Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly. This lets you move from learning individual techniques to building complete applications much faster.

While the functions in this article are powerful building blocks, Agent helps you assemble them into a finished product. It takes your description of an app and handles the code, databases, APIs, and deployment. Instead of piecing together techniques, you can describe the app you want to build, and Agent will take it from idea to working product. For example:

  • A performance monitoring utility that uses a decorator to automatically time and log how long different functions take to run.
  • A bulk processing tool that applies a custom formula, like a lambda function, to every item in a dataset.
  • A dynamic report generator that takes any number of data points (*args) and configuration settings (**kwargs) to produce a formatted summary.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

Even with a solid grasp of the basics, you'll run into a few common pitfalls when defining functions in Python that may require code repair techniques.

Avoiding variable scope issues with the global keyword

A variable's scope determines where it can be accessed. By default, variables created inside a function are local, meaning they only exist within that function. If you try to modify a variable from the outer (global) scope from inside a function, you might accidentally create a new local variable with the same name, which can lead to bugs.

To explicitly modify a global variable from within a function, you need to use the global keyword. Declaring global my_variable at the start of your function tells Python you're referring to the global variable, not creating a new local one. However, it's often better to pass variables as arguments and return values to keep your functions self-contained and easier to debug.

Avoiding unexpected behavior with mutable default arguments like [] and {}

Using a mutable object—like a list [] or a dictionary {}—as a default argument can cause unexpected behavior. Python evaluates default arguments only once when the function is defined, not each time it's called. This means if you modify the default list or dictionary in one call, that change will persist in subsequent calls.

For example, if a function has a parameter my_list=[] and you append an item to it, the next time you call that function without providing your own list, the default will already contain that item. The standard practice to avoid this is to set the default to None and then create a new list or dictionary inside the function if the argument is None.

Preventing stack overflow with function memoization in recursive calls

While recursion is elegant, it comes with a risk. Each time a function calls itself, it adds a new frame to the call stack. If the recursion goes too deep—like calculating a factorial for a very large number—it can exhaust the available memory and cause a stack overflow error, crashing your program.

Memoization is an optimization technique that can prevent this by storing the results of expensive function calls. When the function is called again with the same inputs, it returns the cached result instead of re-computing it. This not only speeds up execution but also reduces the recursion depth, making your function more robust against stack overflows and memory leaks.

Avoiding variable scope issues with the global keyword

A variable's scope determines where it's accessible. When you try to modify a global variable from inside a function, Python assumes you're creating a new local variable. This causes an error if the variable is read before it's assigned locally. The following code demonstrates this common pitfall.

total = 0

def add_to_total(value):
total = total + value
return total

print(add_to_total(5))

The expression total = total + value causes an error because Python tries to read the local variable total before a value has been assigned to it within the function. The following code shows how to address this.

total = 0

def add_to_total(value):
global total
total = total + value
return total

print(add_to_total(5))

The fix is to use the global keyword. By adding global total inside the add_to_total function, you're telling Python not to create a new local variable. Instead, it should modify the total variable that exists in the global scope. This resolves the error by explicitly granting the function access to read and write to the outer variable. For more information on defining global variables in Python, keep an eye out for this issue whenever your functions need to change a global state.

Avoiding unexpected behavior with mutable default arguments like [] and {}

Using a mutable object like a list [] or dictionary {} as a default argument is a common pitfall. Python creates the default value only once, so changes made in one call unexpectedly carry over to the next. The following code demonstrates this confusing behavior.

def add_item(item, items=[]):
items.append(item)
return items

print(add_item("apple"))
print(add_item("banana"))

The first call to add_item permanently changes the default list. When you call it again, you're not starting with a fresh list, which is why "banana" gets added after "apple". See the correct approach below.

def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items

print(add_item("apple"))
print(add_item("banana"))

The solution is to default the items parameter to None. The function then checks if items is None and creates a new empty list [] only when needed. This simple check guarantees that each call to add_item works with a fresh list, preventing one call from affecting another. You should always use this pattern when a function's default argument is a mutable type, like a list or dictionary, to avoid side effects.

Preventing stack overflow with function memoization in recursive calls

While recursion is elegant, it can be inefficient. Each function call adds to the call stack, and deep recursion risks a stack overflow error. The Fibonacci sequence is a classic example where this inefficiency becomes a major problem. The following fibonacci function demonstrates this issue.

def fibonacci(n):
if n <= 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(35))

This implementation of fibonacci is incredibly inefficient because it re-computes the same values over and over. This creates an exponential number of calls, making it too slow for large inputs. See the optimized version below.

def fibonacci(n, memo=None):
if memo is None:
memo = {}
if n in memo:
return memo[n]
if n <= 0:
return 0
elif n == 1:
return 1
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
return memo[n]

print(fibonacci(35))

The optimized fibonacci function uses memoization to avoid re-computing values. It introduces a memo dictionary to cache results. Before running the expensive recursive step, the function checks if the result for n is already in memo. If so, it returns the stored value instantly. This simple cache prevents the exponential number of calls that plague the naive version, making it efficient for large numbers and protecting against stack overflow errors.

Real-world applications

Now that you can sidestep common function pitfalls, you can confidently build practical tools like data processors and calculators.

Processing data with functions and multiple return values

Functions can return multiple values at once, which is an efficient way to bundle related results from a data processing task. Learn more about returning multiple values in Python.

def analyze_temperatures(temperatures):
avg = sum(temperatures) / len(temperatures)
highest = max(temperatures)
lowest = min(temperatures)
return avg, highest, lowest

daily_temps = [72, 65, 78, 80, 68, 74, 77]
avg_temp, max_temp, min_temp = analyze_temperatures(daily_temps)
print(f"Average: {avg_temp:.1f}°F, Highest: {max_temp}°F, Lowest: {min_temp}°F")

The analyze_temperatures function takes a list of numbers and calculates the average, highest, and lowest values using built-in functions like sum() and max(). What's powerful here is how it returns all three results in a single line.

  • When the function returns avg, highest, lowest, Python automatically packs these values into a tuple.
  • You can then immediately unpack that tuple into separate variables—avg_temp, max_temp, and min_temp—on the line where you call the function. This is a clean and direct way to handle related outputs.

Creating a function-based calculator with lambda and dictionaries

You can combine lambda functions with a dictionary to create a clean and dynamic calculator that maps operation names to their corresponding logic.

def calculate(operation, a, b):
operations = {
'add': lambda x, y: x + y,
'subtract': lambda x, y: x - y,
'multiply': lambda x, y: x * y,
'divide': lambda x, y: x / y if y != 0 else "Error: Division by zero"
}

if operation in operations:
return operations[operation](a, b)
return "Unknown operation"

print(calculate('add', 10, 5))
print(calculate('multiply', 3, 4))
print(calculate('divide', 8, 2))

The calculate function acts as a central dispatcher for arithmetic tasks. It contains an operations dictionary where each key, like 'add', is paired with a lambda function that performs the actual math. To learn more about accessing dictionary in Python, this design keeps your code organized by grouping related actions together.

  • When called, the function uses the operation argument to retrieve the correct lambda from the dictionary.
  • It then immediately executes that function with the numbers a and b, returning the result.

This pattern is efficient and makes the code easy to extend since you can add new operations just by updating the dictionary.

Get started with Replit

Turn your new skills into a real tool. Tell Replit Agent to “build a currency converter” or “create a text file analyzer that counts words and characters.”

The Agent writes the code, tests for errors, and deploys the app for you. Start building with Replit.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.