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.
1
defgreet():
2
print("Hello, Data Engineer!")
3
print("Welcome to Python functions.")
4
print("Let us build something great.")
5
6
print("Before calling greet()")
7
8
greet()
9
10
print("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
Write code once and use it anywhere in your program without duplication.
Maintainability
Fix bugs or improve logic in one place, and every caller benefits instantly.
Readability
Give complex operations meaningful names that explain intent at a glance.
Testability
Test each function independently with known inputs and expected outputs.
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.
1
defprocess_record():
2
# These lines are INSIDE
3
print("Starting processing...")
4
print("Validating data...")
5
print("Processing complete!")
6
7
# OUTSIDE the function
8
print("Script has started")
9
10
process_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.
1
defprint_separator():
2
print("="*40)
3
4
defannounce_section(title):
5
print_separator()
6
print(title.upper())
7
print_separator()
8
9
# Call the function multiple times
10
announce_section("Introduction")
11
print("Welcome to our data platform.")
12
print("")
13
14
announce_section("Features")
15
print("Fast processing and reliable storage.")
16
print("")
17
18
announce_section("Conclusion")
19
print("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
2
defgreet_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
8
greet_user("Alice")
9
print("")
10
greet_user("Bob")
11
print("")
12
greet_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.
1
defcalculate_rectangle_area(width,height):
2
area=width*height
3
print("Rectangle dimensions: "+str(width)+" x "+str(height))
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:
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.
1
defdescribe_person(first_name,last_name,age):
2
print(first_name+" "+last_name+" is "+str(age)+" years old")
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.
1
defcreate_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)
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.
defdescribe(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.
1
defcalculate_area(width,height):
2
returnwidth*height
3
4
# Store the returned value in a variable for later use
5
living_room=calculate_area(15,12)
6
bedroom=calculate_area(12,10)
7
kitchen=calculate_area(10,8)
8
9
print("Living room: "+str(living_room)+" sq ft")
10
print("Bedroom: "+str(bedroom)+" sq ft")
11
print("Kitchen: "+str(kitchen)+" sq ft")
12
print("")
13
14
# Use return values directly in calculations
15
total_area=living_room+bedroom+kitchen
16
print("Total area: "+str(total_area)+" sq ft")
17
18
# Use return value directly in an expression
19
cost_per_sqft=25
20
total_cost=calculate_area(20,15)*cost_per_sqft
21
print("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
1
defadd_with_print(a,b):
2
# Shows the sum on screen but returns nothing
3
print(a+b)
4
5
defadd_with_return(a,b):
6
# Returns the sum for the program to use
7
returna+b
8
9
# Demonstrate the difference
10
print("Calling add_with_print(10, 5):")
11
result1=add_with_print(10,5)
12
print("Stored result:",result1)
13
print("Can we multiply it by 2?",result1isNone)
14
print("")
15
16
print("Calling add_with_return(10, 5):")
17
result2=add_with_return(10,5)
18
print("Stored result:",result2)
19
print("Multiplied by 2:",result2*2)
20
print("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.
1
defcategorize_score(score):
2
ifscore<0orscore>100:
3
return"Invalid score"
4
ifscore>=90:
5
return"A - Excellent"
6
ifscore>=80:
7
return"B - Good"
8
ifscore>=70:
9
return"C - Satisfactory"
10
ifscore>=60:
11
return"D - Needs Improvement"
12
return"F - Failing"
13
14
# Test various scores
15
print(categorize_score(95))
16
print(categorize_score(82))
17
print(categorize_score(71))
18
print(categorize_score(55))
19
print(categorize_score(-5))
20
print(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.
1
deflog_message(message):
2
print("[LOG] "+message)
3
# No return statement means returns None
4
5
deflog_warning(message):
6
print("[WARNING] "+message)
7
return
8
9
result1=log_message("System started")
10
result2=log_warning("Low memory")
11
12
print("")
13
print("log_message returned:",result1)
14
print("log_warning returned:",result2)
15
print("Both are None:",result1isNoneandresult2isNone)
>>>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.
1
defcreate_greeting(name):
2
return"Hello, "+name+"! Welcome aboard."
3
4
# Use the result directly in print
5
print(create_greeting("Alice"))
6
7
# Store the result in a variable for multiple uses
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.
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:
1
defextract_name(record):
2
# Extract raw name from record
3
raw_name=record.get("name","")
4
returnraw_name
5
6
defclean_name(raw_name):
7
# Remove extra whitespace and standardize
8
returnraw_name.strip()
9
10
defformat_name(clean_name):
11
# Apply business formatting rules
12
returnclean_name.title()
13
14
defprocess_record(record):
15
# Pipeline: extract -> clean -> format
16
raw=extract_name(record)
17
cleaned=clean_name(raw)
18
formatted=format_name(cleaned)
19
returnformatted
20
21
# Process some records
22
record1={"name":" alice SMITH ","age":28}
23
record2={"name":"BOB johnson","age":35}
24
record3={"name":" carol WILLIAMS ","age":42}
25
26
print(process_record(record1))
27
print(process_record(record2))
28
print(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.
1
defgreet():
2
return"Hello from the function!"
3
4
print("Reference:",greet)
5
print("Type:",type(greet))
6
print("")
7
8
print("Call result:",greet())
9
print("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.
1
defcalculate_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
iflen(numbers)==0:
4
return0.0
5
total=sum(numbers)
6
returntotal/len(numbers)
7
8
# The docstring is stored and accessible
9
print("Function documentation:")
10
print(calculate_average.__doc__)
11
print("")
12
print("Testing the function:")
13
print("Average of [10, 20, 30]:",calculate_average([10,20,30]))
14
print("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:
"""
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
ifamount<=0:
4
raiseValueError("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:
1
defdouble(n):
2
"""Double the input."""
3
returnn*2
4
5
defis_even(n):
6
"""Check if n is even."""
7
returnn%2==0
8
9
defstrip_whitespace(text):
10
"""Strip whitespace."""
11
returntext.strip()
12
13
defget_first_element(items):
14
"""First item or None."""
15
iflen(items)==0:
16
returnNone
17
returnitems[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
Documentation lives right next to the code it describes.
IDE integration
Editors show docstrings as you type function calls, speeding up development.
Team collaboration
New team members understand functions faster without reading implementation.
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
3
defgreet():
4
print("Hello, world!")
5
6
greet()
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.
1
defbad_double(n):
2
# Displays result but returns None
3
print(n*2)
4
5
defgood_double(n):
6
# Returns result for program to use
7
returnn*2
8
9
print("Testing bad_double:")
10
result=bad_double(5)
11
print("Stored:",result)
12
# result * 2 # Error! None is not a number
13
print("")
14
15
# good_double returns usable value
16
print("Testing good_double:")
17
result=good_double(5)
18
print("Stored:",result)
19
print("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.
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
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
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
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
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.
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