Functions: Beginner

GitHub's entire pull request diff engine is built from composable functions that transform raw text into highlighted, reviewable code viewed by 100 million developers. Every feature at GitHub - from code review to Actions workflows to Dependabot alerts - is composed of small functions that each do one thing well and pass results to the next. The power of that architecture starts with exactly what this lesson teaches: how to define a function, give it inputs, and get a result back.

Defining with def

Daily Life
Interviews

Create reusable code blocks with def

A function is a named block of code that performs a specific task. Think of it as a mini-program within your program that you can run whenever you need it. You create a function using the def keyword, which is short for "define." After def, you write the function name, followed by parentheses and a colon. All the code that belongs to the function must be indented beneath the definition line. This indented block is called the function body.

1def greet():
2 print("Hello, Data Engineer!")
3 print("Welcome to Python functions.")
4 print("Let us build something great.")
5
6print("Before calling greet()")
7
8greet()
9
10print("After calling greet()")
>>>Output
Before calling greet()
Hello, Data Engineer!
Welcome to Python functions.
Let us build something great.
After calling greet()

Pay close attention to the execution order in the output above. The code inside greet() only runs when you explicitly call the function with parentheses. Defining a function is like writing a recipe and putting it in a cookbook. Calling the function is like actually following that recipe to cook the dish. You can define a function once and call it as many times as you need throughout your program.

defname():body
def
Define keyword
Tells Python: new function
name
Function name
Identifier to call later
()
Parentheses
Hold parameters or empty
:
Colon marker
Starts the function body
body
Indented body
Code that runs on each call

Why Use Functions?

Before diving deeper into function syntax, it is important to understand why functions matter so much in professional software development. Consider a data pipeline that processes customer orders. Without functions, you might write the same validation logic in dozens of places: check if the order ID is valid, verify the customer exists, confirm the product is in stock, calculate taxes, and format the receipt. Each copy is an opportunity for bugs and inconsistencies.
Reusability
Reusability
Write code once and use it anywhere in your program without duplication.
Maintainability
Maintainability
Fix bugs or improve logic in one place, and every caller benefits instantly.
Readability
Readability
Give complex operations meaningful names that explain intent at a glance.
Testability
Testability
Test each function independently with known inputs and expected outputs.
Organization
Organization
Break large problems into smaller, manageable pieces that are easy to reason about.

Function Naming Conventions

Function names follow the same rules as variable names in Python. They must start with a letter or underscore, can contain only letters, numbers, and underscores, and cannot be reserved Python keywords like if, for, or class. Beyond these technical requirements, there are strong community conventions that make code more readable and professional.

Good Function Names
  • calculate_total_revenue
  • validate_email_address
  • format_user_record
  • parse_json_response
  • clean_transaction_data
Poor Function Names
  • doStuff
  • func1
  • myFunction
  • process
  • x

Good function names use lowercase letters with words separated by underscores, a style called snake_case. They use verbs or verb phrases that describe what the function does. A developer reading calculate_total_revenue immediately understands the purpose without examining the code inside. Poor names like doStuff or func1 force readers to study the implementation to understand what the function does, wasting time and causing confusion.

TIP
Choose function names that describe what the function does, not how it does it. The name calculate_tax is better than multiply_by_rate because it describes the purpose. Implementation details like the specific rate might change, but the function still calculates tax.

Indentation: Function Body

Python uses indentation to determine which code belongs to a function. Every line in the function body must be indented by the same amount, typically four spaces. This is not just a style preference; Python requires consistent indentation. When you reduce the indentation back to the original level, you signal the end of the function body. Any code at that level runs independently, not as part of the function.
1def process_record():
2 # These lines are INSIDE
3 print("Starting processing...")
4 print("Validating data...")
5 print("Processing complete!")
6
7# OUTSIDE the function
8print("Script has started")
9
10process_record()
Inconsistent indentation causes an IndentationError, which stops your program immediately. Python is intentionally strict about this because it uses whitespace to define code structure, unlike languages like Java or JavaScript that use curly braces. This strictness is actually helpful because it forces your code to look exactly like its logical structure, making it easier to read.
Python Quiz

> Define a simple function and call it twice. Pick the keyword that starts a function definition, and the built-in that displays text to the console.

___ greet():
    ___("Hello, World!")

greet()
greet()
return
def
print
func
class

Reusing Functions

The real power of functions emerges when you call them multiple times. Each call executes the entire function body from the beginning, completely independent of previous calls. This means you can reuse the same logic as many times as needed without duplicating code.
1def print_separator():
2 print("=" * 40)
3
4def announce_section(title):
5 print_separator()
6 print(title.upper())
7 print_separator()
8
9# Call the function multiple times
10announce_section("Introduction")
11print("Welcome to our data platform.")
12print("")
13
14announce_section("Features")
15print("Fast processing and reliable storage.")
16print("")
17
18announce_section("Conclusion")
19print("Thank you for reading!")
>>>Output
========================================
INTRODUCTION
========================================
Welcome to our data platform.
 
========================================
FEATURES
========================================
Fast processing and reliable storage.
 
========================================
CONCLUSION
========================================
Thank you for reading!

Notice how we defined print_separator once but called it six times through announce_section. If we want to change the separator character from equals signs to dashes, we modify one line in print_separator, and all six uses update automatically. This is the fundamental value of functions: change once, benefit everywhere.

Parameters and Arguments

Daily Life
Interviews

Pass data into functions flexibly

Most useful functions need input data to work with. A function that calculates tax needs to know the price. A function that formats a name needs to know the name. A function that validates an email needs the email address to check. Parameters are variables that you define in the function signature to receive these input values. When you call the function, you provide arguments, which are the actual values that get assigned to those parameters.
Parameters vs Arguments
  • Parameters are the variable names in the function definition
  • Arguments are the actual values you pass when calling the function
  • Each argument value gets assigned to its corresponding parameter
1# 'name' is a PARAMETER
2def greet_user(name):
3 print("Hello, " + name + "!")
4 print("Welcome to the data engineering team.")
5 print("We are excited to have you here.")
6
7# "Alice" is an ARGUMENT - the value passed
8greet_user("Alice")
9print("")
10greet_user("Bob")
11print("")
12greet_user("Data Engineering Team")
>>>Output
Hello, Alice!
Welcome to the data engineering team.
We are excited to have you here.
 
Hello, Bob!
Welcome to the data engineering team.
We are excited to have you here.
 
Hello, Data Engineering Team!
Welcome to the data engineering team.
We are excited to have you here.

When you call greet_user("Alice"), Python creates a local variable named name and assigns the value "Alice" to it. Inside the function body, name behaves like any other variable, holding that value for the duration of this particular call. When the function finishes, that local variable is destroyed. The next call creates a fresh variable with whatever new value you pass.

Multiple Parameters

Functions frequently need multiple pieces of input data. You can define multiple parameters by separating them with commas in the function signature. When calling the function, you must provide arguments in the same order as the parameters appear in the definition. This is called positional matching because the position of each argument determines which parameter receives it.
1def calculate_rectangle_area(width, height):
2 area = width * height
3 print("Rectangle dimensions: " + str(width) + " x " + str(height))
4 print("Calculated area: " + str(area) + " square units")
5 print("")
6
7# Arguments are matched to parameters by position
8# First argument goes to width, second to height
9calculate_rectangle_area(5, 3)
10calculate_rectangle_area(10, 4)
11calculate_rectangle_area(100, 50)
12
13# Order matters! These produce different results
14calculate_rectangle_area(2, 8)
15calculate_rectangle_area(8, 2)
>>>Output
Rectangle dimensions: 5 x 3
Calculated area: 15 square units
 
Rectangle dimensions: 10 x 4
Calculated area: 40 square units
 
Rectangle dimensions: 100 x 50
Calculated area: 5000 square units
 
Rectangle dimensions: 2 x 8
Calculated area: 16 square units
 
Rectangle dimensions: 8 x 2
Calculated area: 16 square units
 
The first argument always becomes the first parameter, the second argument becomes the second parameter, and so on. In the last two calls, swapping 2 and 8 produces the same area because multiplication is commutative, but the dimensions are reported differently. For functions where order matters semantically, getting arguments in the wrong order causes bugs.

Data Engineering Example

Let us look at a realistic example that formats transaction records for logging. This is the kind of function you might write in a real data pipeline to ensure consistent log formatting across millions of transactions:
1def format_transaction(user_id, amount, currency, status):
2 line = "TXN | User: " + str(user_id)
3 line = line + " | Amount: " + currency + str(amount)
4 line = line + " | Status: " + status
5 print(line)
6
7# Log several transactions with consistent formatting
8format_transaction(12345, 99.99, "$", "completed")
9format_transaction(67890, 150.00, "$", "pending")
10format_transaction(11111, 75.50, "EUR", "completed")
11format_transaction(22222, 200.00, "GBP", "failed")
>>>Output
TXN | User: 12345 | Amount: $99.99 | Status: completed
TXN | User: 67890 | Amount: $150.0 | Status: pending
TXN | User: 11111 | Amount: EUR75.5 | Status: completed
TXN | User: 22222 | Amount: GBP200.0 | Status: failed
Do
  • Wrap repeated logic in a named function
  • Use consistent formatting via a single function
  • Change behavior by editing one function definition
Don't
  • Copy and paste the same code in multiple places
  • Format output differently in every location
  • Hunt down every copy when fixing a bug

Wrong Number of Arguments

Python is strict about argument counts. If you call a function with too few or too many arguments, Python immediately raises a TypeError and stops execution. The error message tells you exactly how many arguments the function expected versus how many you actually provided, making these errors easy to diagnose and fix.
1def describe_person(first_name, last_name, age):
2 print(first_name + " " + last_name + " is " + str(age) + " years old")
3
4# Correct: provides all three required arguments
5describe_person("Ada", "Lovelace", 36)
6
7# Wrong: missing the third argument
8# describe_person("Ada", "Lovelace")
9# TypeError: missing argument
10
11# Wrong: too many arguments
12# describe_person("Ada", "Lovelace", 36, "mathematician")
13# TypeError: takes 3 arguments but 4 given
TIP
When you see a TypeError about arguments, read the message carefully. It tells you the function name, the expected count, and the actual count. This information immediately points you to the mismatch so you can fix it.

Named Arguments

When functions have many parameters, remembering the correct order becomes difficult. Python allows you to pass arguments by name, explicitly specifying which parameter should receive each value. This makes your code more readable and eliminates order-related bugs.
1def create_user_record(name, email, age, department):
2 print("Creating user: " + name)
3 print(" Email: " + email)
4 print(" Age: " + str(age))
5 print(" Department: " + department)
6 print("")
7
8# Using positional arguments (must remember order)
9create_user_record("Alice", "alice@company.com", 28, "Engineering")
10
11# Using named arguments (order does not matter)
12create_user_record(
13 department="Data Science",
14 age=32,
15 name="Bob",
16 email="bob@company.com"
17)
18
19# Mixing positional and named (positional must come first)
20create_user_record("Carol", "carol@company.com", age=25, department="Analytics")
>>>Output
Creating user: Alice
Email: alice@company.com
Age: 28
Department: Engineering
 
Creating user: Bob
Email: bob@company.com
Age: 32
Department: Data Science
 
Creating user: Carol
Email: carol@company.com
Age: 25
Department: Analytics
 
Named arguments are especially valuable when a function has many parameters or when some parameters have similar types that could easily be confused. They serve as inline documentation, making the code self-explanatory.
Python Quiz

> Build a function that joins a name and age into a descriptive string. Pick the function that converts a number to text for concatenation, and the parameter that holds the person's name.

def describe(name, age):
    return ___ + " is " + ___(age)

print(describe("Alice", 28))
repr
name
age
int
str
Parameters make functions reusable. Instead of writing separate functions for every name and age combination, a single parameterized function handles all of them. This is the core principle behind writing functions that generalize rather than hardcode.
Named arguments let callers pass values in any order by specifying the parameter name explicitly. This is especially useful when a function has many parameters and you want the call site to be self-documenting.
Default parameter values mean callers only need to provide the arguments they care about. The function fills in sensible defaults for everything else, reducing the burden on the caller without sacrificing flexibility.

Return Values

Daily Life
Interviews

Send computed results back to callers

Functions become truly powerful when they can send data back to the caller. The return statement lets a function produce a result that you can store in a variable, use in calculations, pass to other functions, or include in expressions. Return values transform functions from actions that just do things into calculators that compute and deliver usable results.

1def calculate_area(width, height):
2 return width * height
3
4# Store the returned value in a variable for later use
5living_room = calculate_area(15, 12)
6bedroom = calculate_area(12, 10)
7kitchen = calculate_area(10, 8)
8
9print("Living room: " + str(living_room) + " sq ft")
10print("Bedroom: " + str(bedroom) + " sq ft")
11print("Kitchen: " + str(kitchen) + " sq ft")
12print("")
13
14# Use return values directly in calculations
15total_area = living_room + bedroom + kitchen
16print("Total area: " + str(total_area) + " sq ft")
17
18# Use return value directly in an expression
19cost_per_sqft = 25
20total_cost = calculate_area(20, 15) * cost_per_sqft
21print("Cost for 20x15 room at $25/sqft: $" + str(total_cost))
>>>Output
Living room: 180 sq ft
Bedroom: 120 sq ft
Kitchen: 80 sq ft
 
Total area: 380 sq ft
Cost for 20x15 room at $25/sqft: $7500

The return statement immediately exits the function and sends the specified value back to where the function was called. That returned value effectively replaces the function call in your code. In the expression calculate_area(20, 15) * cost_per_sqft, Python first calls the function to get 300, then multiplies 300 by 25 to get 7500.

Return vs Print: Key Diff

One of the most common sources of confusion for beginners is the difference between print() and return. They seem similar because both involve outputting information, but they serve completely different purposes. Understanding this distinction is essential for writing useful functions that work correctly in larger programs.

print()
  • Displays text on the screen
  • For human eyes only
  • Function still returns None
  • Cannot use the output in code
  • Side effect for debugging or UI
return
  • Sends value back to caller
  • For program consumption
  • Value can be stored and reused
  • Enables further calculations
  • Produces actual function result
1def add_with_print(a, b):
2 # Shows the sum on screen but returns nothing
3 print(a + b)
4
5def add_with_return(a, b):
6 # Returns the sum for the program to use
7 return a + b
8
9# Demonstrate the difference
10print("Calling add_with_print(10, 5):")
11result1 = add_with_print(10, 5)
12print("Stored result:", result1)
13print("Can we multiply it by 2?", result1 is None)
14print("")
15
16print("Calling add_with_return(10, 5):")
17result2 = add_with_return(10, 5)
18print("Stored result:", result2)
19print("Multiplied by 2:", result2 * 2)
20print("Used in another calculation:", result2 + 100)
>>>Output
Calling add_with_print(10, 5):
15
Stored result: None
Can we multiply it by 2? True
 
Calling add_with_return(10, 5):
Stored result: 15
Multiplied by 2: 30
Used in another calculation: 115

Look carefully at the output. add_with_print displays 15 on the screen, but when we store the result, we get None. We cannot do math with None. Meanwhile, add_with_return does not display anything itself, but the returned value 15 can be stored, multiplied, added, or used in any way we need.

Do
  • Use return to produce values your program needs
  • Store returned values in variables for later use
  • Use print() only for debugging or user-facing output
Don't
  • Use print() when you need to use the result in code
  • Forget that print() makes the function return None
  • Mix up displaying data with computing data

Return Exits Immediately

When Python executes a return statement, it immediately exits the function. Any code after the return statement never runs. This behavior is useful for early exits when you have already determined the result and do not need to continue processing.

1def categorize_score(score):
2 if score < 0 or score > 100:
3 return "Invalid score"
4 if score >= 90:
5 return "A - Excellent"
6 if score >= 80:
7 return "B - Good"
8 if score >= 70:
9 return "C - Satisfactory"
10 if score >= 60:
11 return "D - Needs Improvement"
12 return "F - Failing"
13
14# Test various scores
15print(categorize_score(95))
16print(categorize_score(82))
17print(categorize_score(71))
18print(categorize_score(55))
19print(categorize_score(-5))
20print(categorize_score(150))
>>>Output
A - Excellent
B - Good
C - Satisfactory
F - Failing
Invalid score
Invalid score
Each condition checks the score and returns immediately when it matches. For a score of 82, Python checks if it is invalid (no), then checks if it is 90 or above (no), then checks if it is 80 or above (yes), and immediately returns "B - Good". It never checks the remaining conditions. This pattern is efficient and easy to read.

Try changing the return keyword below to see how it affects function behavior. What happens when you use print instead of return?

Fill in the Blank

> A double function computes n * 2, and the caller stores the result. Pick whether to use return or print inside the function and see what the caller actually receives.

def double(n):
     n * 2

result = double(5)
print(result)

Functions Without Return

If a function has no return statement, or uses return without a value, Python automatically returns None. None is a special Python value that represents "nothing" or "no value." Functions that return None are used for their side effects, like printing output, writing files, or modifying data structures.

1def log_message(message):
2 print("[LOG] " + message)
3 # No return statement means returns None
4
5def log_warning(message):
6 print("[WARNING] " + message)
7 return
8
9result1 = log_message("System started")
10result2 = log_warning("Low memory")
11
12print("")
13print("log_message returned:", result1)
14print("log_warning returned:", result2)
15print("Both are None:", result1 is None and result2 is None)
>>>Output
[LOG] System started
[WARNING] Low memory
 
log_message returned: None
log_warning returned: None
Both are None: True
TIP
Functions that perform actions like logging, saving files, or sending emails typically return None because their purpose is the action itself, not a computed value. This is a valid and common pattern in Python programming.

Calling and Storing

Daily Life
Interviews

Chain function calls into pipelines

Defining a function creates it but does not execute it. The function body only runs when you explicitly call the function by writing its name followed by parentheses containing any required arguments. You can call a function as many times as needed, and each call is independent. The results can be used directly, stored in variables, or passed to other functions.
1def create_greeting(name):
2 return "Hello, " + name + "! Welcome aboard."
3
4# Use the result directly in print
5print(create_greeting("Alice"))
6
7# Store the result in a variable for multiple uses
8message = create_greeting("Bob")
9print(message)
10print(message.upper())
11print("Message length:", len(message))
12print("")
13
14# Use the result as part of a larger expression
15combined = create_greeting("Carol") + " " + create_greeting("Dave")
16print(combined)
>>>Output
Hello, Alice! Welcome aboard.
Hello, Bob! Welcome aboard.
HELLO, BOB! WELCOME ABOARD.
Message length: 27
 
Hello, Carol! Welcome aboard. Hello, Dave! Welcome aboard.

Function calls can appear anywhere Python expects a value: on the right side of an assignment, inside print(), as an argument to another function, or as part of an arithmetic expression. Python evaluates the function call first, gets the returned value, and then uses that value in the surrounding expression.

Functions Calling Functions

Functions can call other functions. This is how you build complex programs from simple, well-tested pieces. Each function handles one specific task, and higher-level functions combine them to accomplish larger goals. This approach is called functional decomposition, and it is fundamental to writing maintainable software.
1def calculate_subtotal(price, quantity):
2 return price * quantity
3
4def calculate_tax(amount, tax_rate):
5 return amount * tax_rate
6
7def calculate_total(price, quantity, tax_rate):
8 subtotal = calculate_subtotal(price, quantity)
9 tax = calculate_tax(subtotal, tax_rate)
10 total = subtotal + tax
11 return total
12
13def format_receipt(item_name, price, quantity, tax_rate):
14 subtotal = calculate_subtotal(price, quantity)
15 tax = calculate_tax(subtotal, tax_rate)
16 total = calculate_total(price, quantity, tax_rate)
17
18 print("Item: " + item_name)
19 print("Price: $" + str(price) + " x " + str(quantity))
20 print("Subtotal: $" + str(subtotal))
21 print("Tax (8%): $" + str(round(tax, 2)))
22 print("Total: $" + str(round(total, 2)))
23
24format_receipt("Widget Pro", 29.99, 3, 0.08)
>>>Output
Item: Widget Pro
Price: $29.99 x 3
Subtotal: $89.97
Tax (8%): $7.2
Total: $97.17
01
Single Purpose
Each function does exactly one thing well, making it easy to understand.
02
Testable Units
Test calculate_tax independently with known values and expected outputs.
03
Reusable Parts
Use calculate_subtotal anywhere you need it across your codebase.
04
Self-Documenting
Function names explain what each step does without needing comments.
05
Easy Maintenance
Change tax calculation in one place and every caller automatically updates.

Real Data Pipeline Pattern

Professional data engineers structure ETL pipelines as chains of function calls. Each function handles one transformation step, making the pipeline easy to understand, test, and modify. Here is a simplified example showing how data flows through functions:
1def extract_name(record):
2 # Extract raw name from record
3 raw_name = record.get("name", "")
4 return raw_name
5
6def clean_name(raw_name):
7 # Remove extra whitespace and standardize
8 return raw_name.strip()
9
10def format_name(clean_name):
11 # Apply business formatting rules
12 return clean_name.title()
13
14def process_record(record):
15 # Pipeline: extract -> clean -> format
16 raw = extract_name(record)
17 cleaned = clean_name(raw)
18 formatted = format_name(cleaned)
19 return formatted
20
21# Process some records
22record1 = {"name": " alice SMITH ", "age": 28}
23record2 = {"name": "BOB johnson", "age": 35}
24record3 = {"name": " carol WILLIAMS ", "age": 42}
25
26print(process_record(record1))
27print(process_record(record2))
28print(process_record(record3))
>>>Output
Alice Smith
Bob Johnson
Carol Williams
TIP
This extract-transform-load pattern scales to pipelines processing millions of records. Each function is tested independently, and the pipeline is just a series of function calls. When requirements change, you modify or replace individual functions without touching others.

Function Reference vs Call

There is an important distinction between a function reference and a function call. Writing my_func without parentheses gives you the function object itself, which you can pass around or inspect. Writing my_func() with parentheses actually executes the function and gives you its return value.

1def greet():
2 return "Hello from the function!"
3
4print("Reference:", greet)
5print("Type:", type(greet))
6print("")
7
8print("Call result:", greet())
9print("Type:", type(greet()))
>>>Output
Reference: <function greet at 0x...>
Type: <class 'function'>
 
Call result: Hello from the function!
Type: <class 'str'>
Forgetting the parentheses is a common mistake. Instead of getting the result you expect, you get the function object, which usually causes confusing errors later in your code. Always include parentheses when you want to execute a function and use its return value.

Docstrings

Daily Life
Interviews

Document functions for team clarity

When you work on a team or return to your own code after weeks or months, documentation becomes essential. Python provides docstrings, which are special strings that describe what a function does, what parameters it expects, and what it returns. Docstrings are written as the first statement in a function body using triple quotes, and Python stores them for tools and developers to access.
1def calculate_average(numbers):
2 """ Calculate the arithmetic mean of a list of numbers. Args: numbers: A list of numeric values to average Returns: The average as a float, or 0.0 if the list is empty """
3 if len(numbers) == 0:
4 return 0.0
5 total = sum(numbers)
6 return total / len(numbers)
7
8# The docstring is stored and accessible
9print("Function documentation:")
10print(calculate_average.__doc__)
11print("")
12print("Testing the function:")
13print("Average of [10, 20, 30]:", calculate_average([10, 20, 30]))
14print("Average of empty list:", calculate_average([]))
>>>Output
Function documentation:
 
Calculate the arithmetic mean of a list of numbers.
 
Args:
numbers: A list of numeric values to average
 
Returns:
The average as a float, or 0.0 if the list is empty
 
Testing the function:
Average of [10, 20, 30]: 20.0
Average of empty list: 0.0

The docstring appears immediately after the function definition, enclosed in triple quotes. Python stores it in the __doc__ attribute. IDEs show docstrings when you hover over function names, and tools like help() display them. This makes docstrings a powerful form of inline documentation that stays with the code.

Docstring Conventions

While Python does not enforce a specific docstring format, several conventions are widely used in the industry. The Sphinx/reStructuredText style and NumPy style are particularly popular in data engineering. Here is a practical template that covers the most important information:
1def process_transaction(user_id, amount, currency="USD"):
2 """ Process a financial transaction and return a confirmation. This function validates the transaction amount, applies the current exchange rate for the specified currency, and returns a formatted confirmation string. Args: user_id: Integer identifier for the user account amount: Transaction amount as a positive float currency: Three-letter ISO currency code (default: USD) Returns: A formatted string confirming the transaction details Raises: ValueError: If amount is negative or zero KeyError: If currency code is not recognized Example: >>> process_transaction(12345, 99.99) 'Transaction confirmed: User 12345, $99.99 USD' """
3 if amount <= 0:
4 raise ValueError("Amount must be positive")
5 return "Transaction confirmed: User " + str(user_id) + ", $" + str(amount) + " " + currency
SummaryArgs:Returns:Raises:Example:
Summary
One-line intro
Brief purpose of function
Args:
Parameter docs
Each param with its type
Returns:
Return value
What the function gives back
Raises:
Exceptions list
Errors the function may throw
Example:
Usage example
Shows how to call it right

Simple One-Line Docstrings

Not every function needs extensive documentation. For simple functions with obvious behavior, a one-line docstring is sufficient. It should fit on a single line with the triple quotes and describe what the function does:
1def double(n):
2 """Double the input."""
3 return n * 2
4
5def is_even(n):
6 """Check if n is even."""
7 return n % 2 == 0
8
9def strip_whitespace(text):
10 """Strip whitespace."""
11 return text.strip()
12
13def get_first_element(items):
14 """First item or None."""
15 if len(items) == 0:
16 return None
17 return items[0]
TIP
Write docstrings as if explaining to a colleague who has never seen your code. If they can understand what the function does, what to pass in, and what comes out without reading the implementation, your docstring is effective.

Why Docstrings Matter

Docstrings are an investment that pays dividends every time someone (including future you) needs to understand or use your function.
Self-documenting
Self-documenting
Documentation lives right next to the code it describes.
IDE integration
IDE integration
Editors show docstrings as you type function calls, speeding up development.
Team collaboration
Team collaboration
New team members understand functions faster without reading implementation.
API design clarity
API design clarity
Writing docs first helps clarify what functions should do before coding.
Python itself uses docstrings extensively, and you can explore them interactively.

Common Mistakes

Even experienced developers make these mistakes occasionally. Knowing about them helps you recognize and fix them quickly when they appear in your own code or during debugging sessions.

Forgetting to Call Function

Defining a function creates it but does not execute it. You must explicitly call the function with parentheses to run its code. This mistake often happens when copying code from tutorials or documentation.
Wrong
  • def process_data():
  • print("Processing...")
  • clean_records()
  • # Function defined but never called
  • # Nothing happens when you run this!
Correct
  • def process_data():
  • print("Processing...")
  • clean_records()
  • process_data() # Call the function!
  • # Now it actually runs

Forgetting return Statement

When you need to use a function's result, you must return it. A common mistake is computing a value but forgetting to return it, which causes the function to return None instead of the expected result.
Wrong
  • def calculate_total(a, b):
  • result = a + b
  • # Forgot to return!
  • total = calculate_total(5, 3)
  • # total is None, not 8
Correct
  • def calculate_total(a, b):
  • result = a + b
  • return result
  • total = calculate_total(5, 3)
  • # total is 8

Calling Before Defining

Python executes code from top to bottom. If you try to call a function before Python has seen its definition, you get a NameError because the function does not exist yet in that moment.
1# greet() # NameError
2
3def greet():
4 print("Hello, world!")
5
6greet()

print vs return Mistake

This mistake causes subtle bugs because the function seems to work when you run it, but the result cannot be used elsewhere in your program.
1def bad_double(n):
2 # Displays result but returns None
3 print(n * 2)
4
5def good_double(n):
6 # Returns result for program to use
7 return n * 2
8
9print("Testing bad_double:")
10result = bad_double(5)
11print("Stored:", result)
12# result * 2 # Error! None is not a number
13print("")
14
15# good_double returns usable value
16print("Testing good_double:")
17result = good_double(5)
18print("Stored:", result)
19print("Doubled again:", result * 2)
>>>Output
Testing bad_double:
10
Stored: None
 
Testing good_double:
Stored: 10
Doubled again: 20
Can you fix this buggy function? It is supposed to return the sum but has an extra tile that breaks the logic.
Debug Challenge

> This add function computes the sum correctly but has an unnecessary print statement that uses invalid syntax. Remove it so the function cleanly returns the result.

The function prints AND returns. Remove the unnecessary print tile.

Object vs Call Confusion

Forgetting parentheses gives you the function object instead of calling it. This usually causes confusing errors later when you try to use the result.
Correct
  • result = calculate_tax(100)
  • greeting = get_greeting("Alice")
  • print(format_record(data))
Wrong
  • result = calculate_tax # Function object!
  • greeting = get_greeting # Missing parens!
  • print(format_record) # Reference only!
PUTTING IT ALL TOGETHER

> You are a junior data engineer at Stripe refactoring a 200-line data processing script into named functions so your team can reuse individual transformation steps across multiple payment pipelines.

def packages each transformation step into a named unit so it can be called from any pipeline without copy-pasting logic.
Parameters pass the specific record or value into the function so it generalizes across all input rows and file formats.
return sends the transformed result back to the caller for storage, further processing, or downstream pipeline stages.
Docstrings document what each function expects and returns so teammates can use it without reading the full implementation.
KEY TAKEAWAYS
Use def to define functions that package reusable code blocks
Parameters are variables in the definition; arguments are values you pass when calling
return sends a value back to the caller; without it, the function returns None
Function calls require parentheses; without them you get the function object itself
Functions can call other functions to compose complex behavior from simple pieces
Use print() for displaying information; use return for computed values programs need
Docstrings document what functions do, their parameters, and return values
Functions must be defined before the line of code that calls them
Good function names are verbs in snake_case that describe the action performed
Argument count must exactly match parameter count or Python raises TypeError

Write once, use everywhere

Category
Python
Difficulty
beginner
Duration
38 minutes
Challenges
0 hands-on challenges

Topics covered: Defining with def, Parameters and Arguments, Return Values, Calling and Storing, Docstrings

Lesson Sections

  1. Defining with def (concepts: pyFuncDef)

    Why Use Functions? Before diving deeper into function syntax, it is important to understand why functions matter so much in professional software development. Consider a data pipeline that processes customer orders. Without functions, you might write the same validation logic in dozens of places: check if the order ID is valid, verify the customer exists, confirm the product is in stock, calculate taxes, and format the receipt. Each copy is an opportunity for bugs and inconsistencies. Function N

  2. Parameters and Arguments

    Most useful functions need input data to work with. A function that calculates tax needs to know the price. A function that formats a name needs to know the name. A function that validates an email needs the email address to check. Parameters are variables that you define in the function signature to receive these input values. When you call the function, you provide arguments, which are the actual values that get assigned to those parameters. Multiple Parameters Functions frequently need multip

  3. Return Values

    Return vs Print: Key Diff Return Exits Immediately Each condition checks the score and returns immediately when it matches. For a score of 82, Python checks if it is invalid (no), then checks if it is 90 or above (no), then checks if it is 80 or above (yes), and immediately returns "B - Good". It never checks the remaining conditions. This pattern is efficient and easy to read. Functions Without Return

  4. Calling and Storing

    Defining a function creates it but does not execute it. The function body only runs when you explicitly call the function by writing its name followed by parentheses containing any required arguments. You can call a function as many times as needed, and each call is independent. The results can be used directly, stored in variables, or passed to other functions. Functions Calling Functions Functions can call other functions. This is how you build complex programs from simple, well-tested pieces.

  5. Docstrings

    When you work on a team or return to your own code after weeks or months, documentation becomes essential. Python provides docstrings, which are special strings that describe what a function does, what parameters it expects, and what it returns. Docstrings are written as the first statement in a function body using triple quotes, and Python stores them for tools and developers to access. Docstring Conventions While Python does not enforce a specific docstring format, several conventions are wide