Databricks processes petabytes of data daily using functional patterns where map() applies the same transformation to millions of records simultaneously, and filter() removes unwanted rows without a single for loop. The data engineering teams at companies like Instacart use these tools to clean, reshape, and validate raw data in pipelines that run thousands of times per day. lambda functions let you define a transformation inline without naming it, keeping pipeline code compact and readable. The map(), filter(), and lambda patterns in this lesson are the building blocks of modern data transformation pipelines.
Defining Functions
Daily Life
Interviews
Build reusable functions with def
Functions are reusable blocks of code that perform a specific task. They help you organize your programs and avoid repeating the same code.
What is a Function?
You have already used functions without realizing it. Every time you write print("Hello") or len("text"), you are calling a function. These are built-in functions that come with Python, created by Python developers to handle common tasks. The print() function displays output to the screen. The len() function calculates the length of a string or collection. You do not need to know how these functions work internally; you just need to know what to give them and what they give back.
1
# Using built-in functions you already know
2
message="Hello, Python!"
3
print(message)
4
print(len(message))
5
6
# More built-in functions
7
numbers=[5,2,8,1,9]
8
print(max(numbers))
9
print(min(numbers))
10
print(sum(numbers))
>>>Output
Hello, Python!
14
9
1
25
Each function call follows the same pattern: write the function name, open parentheses, provide any data the function needs (called arguments), and close parentheses. The function processes the arguments and may produce a result you can use.
01
Function Name
Identifies which function to run
02
Parentheses ()
Signal that you are calling the function
03
Arguments
Values inside the parentheses provide input data
04
Return Value
The function may send a result back for you to store or use
Why Create Functions?
Built-in functions cover common operations, but your programs have unique requirements. You might need to calculate taxes for your specific business rules, format names according to your company standards, or validate data in ways Python cannot predict. Creating your own functions lets you package this custom logic and reuse it throughout your program.
Consider a program that needs to calculate the area of rectangles in several places. Without functions, you would repeat the calculation each time:
1
# First calculation
2
width1=5
3
height1=3
4
area1=width1*height1
5
print("Room 1 area:",area1)
6
7
width2=10
8
height2=4
9
area2=width2*height2
10
print("Room 2 area:",area2)
11
12
width3=7
13
height3=6
14
area3=width3*height3
15
print("Room 3 area:",area3)
The calculation width * height appears three times. This seems harmless now, but imagine the formula becomes more complex, perhaps adding a margin for waste or converting units. You would need to update every location where you copied the formula. Functions eliminate this duplication by letting you define the logic once and call it from anywhere.
Reusability
Write code once and call it from anywhere in your program
Maintainability
Fix bugs in a single place instead of hunting down every copy
Readability
Give complex logic a meaningful name that explains its purpose
Testability
Test each function independently to catch errors early
Organization
Break large problems into smaller, manageable pieces
To create a function, use the def keyword (short for "define"), followed by the function name, parentheses, and a colon. The lines of code that belong to the function must be indented. This indented block is called the function body, and it contains the code that runs when you call the function.
1
# Define a simple function
2
defgreet():
3
print("Hello, World!")
4
print("Welcome to Python functions!")
5
6
print("Before calling the function")
7
8
greet()
9
10
print("After calling the function")
>>>Output
Before calling the function
Hello, World!
Welcome to Python functions!
After calling the function
Notice the order of output. The code inside the function only runs when you call it with greet(). Defining a function is like writing a recipe; calling it is like actually cooking the dish. You can call a function as many times as you want, and each call executes the function body from the beginning.
1
defannounce():
2
print("*"*20)
3
print("IMPORTANT MESSAGE")
4
print("*"*20)
5
6
announce()
7
print("First message")
8
announce()
9
print("Second message")
10
announce()
>>>Output
********************
IMPORTANT MESSAGE
********************
First message
********************
IMPORTANT MESSAGE
********************
Second message
********************
IMPORTANT MESSAGE
********************
Anatomy of a Function
Every function definition has the same structure. Understanding each part helps you write functions correctly and read other people's code:
1
deffunction_name():
2
# Function body
3
# Indented
4
statement_1
5
statement_2
6
statement_3
7
8
# Outside function
defname():body
def
Keyword
Tells Python: new function
name
Identifier
How you call the function
()
Parentheses
Required, even if empty
:
Colon
Ends the function header
body
Indented Block
Runs when function called
Function Naming Conventions
Function names follow the same rules as variable names. They must start with a letter or underscore, can contain letters, numbers, and underscores, and cannot be Python keywords. Beyond these rules, there are strong conventions that make code readable:
•Good Function Names
calculate_area
format_user_name
validate_email
convert_temperature
get_total_price
•Poor Function Names
doStuff
f
myFunction
process
x
Good function names are lowercase with words separated by underscores (called snake_case). They use verbs or verb phrases that describe what the function does. Someone reading calculate_area immediately understands the purpose without looking at the code inside.
TIP
A function name should describe what it does, not how it does it. "calculate_tax" is better than "multiply_by_rate" because it describes the purpose, not the implementation detail that might change.
Indentation is Mandatory
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). Inconsistent indentation causes errors:
1
# CORRECT: Consistent indentation
2
defgreet():
3
print("Hello!")
4
print("Welcome!")
5
print("Have a great day!")
6
7
# WRONG: Would cause IndentationError
8
defgreet_broken():
9
print("Hello!")
10
# Error! Wrong indentation
11
print("This line has wrong indentation!")
12
print("Have a great day!")
When you reduce the indentation back to the original level, you signal the end of the function body. The next unindented line is outside the function and runs independently of any function call.
Python Quiz
> Build a function that formats a test score as a percentage string. Pick the function that rounds to one decimal place and the function that converts the number to text for concatenation.
Functions are the foundation of organized Python code. Every professional codebase consists largely of function definitions and function calls that compose to produce complex behavior from simple pieces.
Python uses indentation to define function bodies, making the structure visually clear. The function body runs only when you call the function, not when you define it, just as a recipe only produces food when you cook it.
Good function names describe what the function does, not how it does it. A name like calculate_tax communicates intent instantly; a name like multiply_by_rate exposes an implementation detail that might change.
Function Parameters
Daily Life
Interviews
Pass data into functions with params
Most functions need input data to work with. A function that calculates area needs to know the dimensions. A function that formats names needs to know the name. Parameters are variables that receive values when you call the function. They appear inside the parentheses in the function definition and act as placeholders for the actual values you will provide.
Two terms that beginners often mix up are parameters and arguments. They refer to different sides of the same coin.
ParameterArgument
Parameter
In Definition
Variable in the def line
Argument
In Call
Actual value you pass in
1
# name is a PARAMETER - receives a value
2
defgreet(name):
3
print("Hello, "+name+"!")
4
print("Nice to meet you, "+name+".")
5
6
# "Alice" is an ARGUMENT
7
greet("Alice")
8
print("---")
9
greet("Bob")
10
print("---")
11
greet("Charlie")
>>>Output
Hello, Alice!
Nice to meet you, Alice.
---
Hello, Bob!
Nice to meet you, Bob.
---
Hello, Charlie!
Nice to meet you, Charlie.
When you call greet("Alice"), Python assigns the value "Alice" to the parameter name. Inside the function, name behaves like any other variable, holding that value for the duration of the function call.
Multiple Parameters
Functions can accept multiple parameters, separated by commas. When calling the function, you must provide arguments in the same order as the parameters are defined:
1
defcalculate_area(width,height):
2
area=width*height
3
print("Width: "+str(width))
4
print("Height: "+str(height))
5
print("Area: "+str(area))
6
print("---")
7
8
# Arguments must match parameter order
9
calculate_area(5,3)
10
calculate_area(10,4)
11
calculate_area(7,6)
>>>Output
Width: 5
Height: 3
Area: 15
---
Width: 10
Height: 4
Area: 40
---
Width: 7
Height: 6
Area: 42
---
The first argument becomes the first parameter, the second argument becomes the second parameter, and so on. This is called positional matching because the position determines which parameter receives which value.
Default Parameter Values
Sometimes you want a parameter to have a sensible default value that the caller can override if needed. This makes parameters optional. Default values are specified with an equals sign in the function definition:
1
defgreet(name,greeting="Hello"):
2
print(greeting+", "+name+"!")
3
4
# Using the default greeting
5
greet("Alice")
6
7
# Providing a custom greeting
8
greet("Bob","Hi")
9
greet("Charlie","Good morning")
10
greet("Diana","Welcome")
>>>Output
Hello, Alice!
Hi, Bob!
Good morning, Charlie!
Welcome, Diana!
The parameter greeting has a default value of "Hello". When you call greet("Alice") with only one argument, greeting uses its default. When you provide a second argument, that value overrides the default.
TIP
Parameters with default values must come after parameters without defaults. Writing def greet(greeting="Hello", name) causes a syntax error because Python cannot determine which argument goes where.
Flexible Formatting Example
Default parameters are perfect for functions that need flexibility. Here is a formatting function where most calls use the defaults, but callers can customize when needed:
1
defformat_price(amount,currency="$",decimals=2):
2
formatted=round(amount,decimals)
3
returncurrency+str(formatted)
4
5
# Most common use - just the amount
6
print(format_price(19.99))
7
print(format_price(100))
8
9
# Override currency
10
print(format_price(50,"EUR "))
11
12
# Override both
13
print(format_price(1234.5678,"GBP ",3))
>>>Output
$19.99
$100
EUR 50
GBP 1234.568
Callers who just need dollar formatting with two decimals pass only the amount. Callers with special needs can override one or both defaults. This pattern appears throughout Python's standard library.
Wrong Number of Arguments
If you call a function with too few or too many arguments, Python raises a TypeError. The error message tells you exactly how many arguments were expected versus how many you provided:
1
defgreet(first_name,last_name):
2
print("Hello, "+first_name+" "+last_name+"!")
3
4
# Correct: provides both required arguments
5
greet("Ada","Lovelace")
6
7
# Wrong: missing the second argument
8
# greet("Ada")
9
# TypeError: missing required argument
10
11
# Wrong: too many arguments
12
# greet("Ada", "Lovelace", "PhD")
13
# TypeError: takes 2 arguments but 3 given
Read these error messages carefully. They tell you the function name, how many arguments it expects, and how many you provided. This information helps you quickly identify and fix the mismatch.
Test your understanding of parameters and default values by experimenting with different function calls below.
Fill in the Blank
> You are defining a greet function with a default greeting parameter. Pick a default value and see how it affects the output when the caller omits the argument.
Parameters make functions flexible. A function with well-chosen parameters can be reused in many different contexts without any changes to its internal logic.
Default parameter values are a powerful technique for making functions easier to use. They let callers omit arguments they do not need to customize, reducing boilerplate in common cases.
Parameters without defaults are required; parameters with defaults are optional. Required parameters must always come before optional ones in the function signature so Python can unambiguously match arguments to parameters.
Return Values
Daily Life
Interviews
Send results back with return values
Functions can send data back to the caller using the return statement. This is how functions produce results that you can store in variables, use in calculations, or pass to other functions. Return values transform functions from mere action performers into calculators that produce usable results.
1
defcalculate_area(width,height):
2
returnwidth*height
3
4
# Store the returned value in a variable
5
area1=calculate_area(5,3)
6
area2=calculate_area(10,4)
7
8
print("First area: "+str(area1))
9
print("Second area: "+str(area2))
10
print("Combined area: "+str(area1+area2))
11
12
# Use directly in expressions
13
total_cost=calculate_area(12,8)*25
14
print("Cost at $25 per sq unit: $"+str(total_cost))
>>>Output
First area: 15
Second area: 40
Combined area: 55
Cost at $25 per sq unit: $2400
The return statement immediately exits the function and sends the specified value back to where the function was called. That value replaces the function call in the expression, as if you had written the value directly.
Return vs Print: Key Diff
Beginners often confuse print() and return. They seem similar because both involve output, but they serve completely different purposes. Understanding this distinction is fundamental to writing useful functions:
•print()
Displays text on screen
For human consumption only
Function returns None
Cannot use the value later
Side effect, not a result
•return
Sends value back to caller
For program consumption
Value can be stored and used
Enables further calculations
Actual function result
1
defadd_with_print(a,b):
2
# Shows the sum but returns nothing
3
print(a+b)
4
5
defadd_with_return(a,b):
6
# Returns the sum for further use
7
returna+b
8
9
# Try to use the results
10
result1=add_with_print(3,5)
11
result2=add_with_return(3,5)
12
13
print("result1 is: "+str(result1))
14
print("result2 is: "+str(result2))
15
print("result2 doubled: "+str(result2*2))
>>>Output
8
result1 is: None
result2 is: 8
result2 doubled: 16
add_with_print displays 8 on the screen but returns None, so result1 is useless. add_with_return does not display anything but returns 8, which we can store, display, or use in further calculations.
Functions Without Return
If a function does not have a return statement, or if return is used without a value, the function automatically returns None. None is Python's special value representing "nothing" or "no value."
1
defsay_hello():
2
print("Hello!")
3
# No return statement
4
5
defsay_goodbye():
6
print("Goodbye!")
7
return
8
9
result1=say_hello()
10
result2=say_goodbye()
11
12
print("Result 1:",result1)
13
print("Result 2:",result2)
14
print("Both are None:",result1isNoneandresult2isNone)
>>>Output
Hello!
Goodbye!
Result 1: None
Result 2: None
Both are None: True
Functions without return values are used for their "side effects" such as printing messages, writing to files, or modifying data. They perform actions rather than computing results. Both patterns are valid; the choice depends on the function's purpose.
Return Exits Immediately
When Python encounters a return statement, it immediately exits the function. Any code after the return statement never runs. This is useful for early exits when you have already determined the result:
1
defcheck_age(age):
2
ifage<0:
3
return"Invalid: age cannot be negative"
4
ifage<18:
5
return"Minor"
6
ifage<65:
7
return"Adult"
8
return"Senior"
9
10
print(check_age(-5))
11
print(check_age(15))
12
print(check_age(30))
13
print(check_age(70))
>>>Output
Invalid: age cannot be negative
Minor
Adult
Senior
Each condition checks the age and returns immediately if it matches. The function never runs more code than necessary. When a return is executed, Python skips all remaining code in the function.
Scope and Variables
Daily Life
Interviews
Control variable visibility in code
Scope determines where a variable exists and can be accessed. Understanding scope prevents bugs where variables unexpectedly have wrong values or do not exist when you expect them to. Python has two main scopes relevant to functions: local scope (inside a function) and global scope (outside all functions).
Scope Definition
Local scope - variables created inside a function, only visible there
Global scope - variables created outside all functions, visible everywhere
A variable's scope is determined by where it is created
Local Variables
Variables created inside a function are local to that function. They are created when the function starts running and destroyed when the function finishes. They do not exist outside the function and cannot be accessed from elsewhere:
1
defcalculate():
2
result=100
3
multiplier=5
4
total=result*multiplier
5
print("Inside function:",total)
6
7
calculate()
8
# print(result) # NameError! result does not exist here
9
# print(multiplier) # NameError! multiplier does not exist here
10
# print(total) # NameError! total does not exist here
This isolation is intentional and beneficial. Each function has its own private workspace. You can use the same variable name in different functions without conflict because each function has its own local scope.
1
deffunction_one():
2
# x is local to function_one
3
x=10
4
print("function_one x:",x)
5
6
deffunction_two():
7
# x is local to function_two, completely separate
8
x=99
9
print("function_two x:",x)
10
11
function_one()
12
function_two()
13
function_one()
>>>Output
function_one x: 10
function_two x: 99
function_one x: 10
Global Variables
Variables created outside any function are global. They exist for the entire program and can be read from inside functions. However, you cannot modify them from inside a function without special syntax (which we will cover in the intermediate lesson).
1
TAX_RATE=0.08
2
COMPANY_NAME="ACME Corp"
3
4
defcalculate_tax(price):
5
# Can READ global variables
6
tax=price*TAX_RATE
7
returntax
8
9
defprint_receipt(item,price):
10
# Can READ globals too
11
tax=calculate_tax(price)
12
total=price+tax
13
print(COMPANY_NAME)
14
print("-"*20)
15
print(item+": $"+str(price))
16
print("Tax: $"+str(tax))
17
print("Total: $"+str(total))
18
19
print_receipt("Widget",100)
>>>Output
ACME Corp
--------------------
Widget: $100
Tax: $8.0
Total: $108.0
Global variables named in ALL_CAPS indicate constants that should not change. Functions can read these values freely, making it easy to share configuration across your program.
Parameters as Local Vars
Parameters are local variables too. When you call a function, Python creates local variables from the parameters and assigns the argument values to them. Modifying a parameter inside the function does not affect variables outside:
1
defattempt_to_modify(x):
2
print("Inside function, before:",x)
3
# Modifies the local x only
4
x=x*2
5
print("Inside function, after:",x)
6
7
number=5
8
print("Before function call:",number)
9
attempt_to_modify(number)
10
# Unchanged!
11
print("After function call:",number)
>>>Output
Before function call: 5
Inside function, before: 5
Inside function, after: 10
After function call: 5
The parameter x inside the function is a separate variable that happens to start with the same value as number. Changing x does not change number. This is called pass by value for simple types like numbers and strings.
TIP
This behavior is usually what you want. Functions that do not modify external state are easier to understand, test, and debug. Use return values to send results back instead of modifying outside variables.
Scope Lookup Order
When you use a variable name, Python searches for it in order: first local scope, then enclosing scopes (for nested functions), then global scope, then built-in scope. It uses the first match it finds:
1
x="global"
2
3
defouter():
4
x="outer"
5
6
definner():
7
x="inner"
8
print("inner sees:",x)
9
10
inner()
11
print("outer sees:",x)
12
13
outer()
14
print("global sees:",x)
>>>Output
inner sees: inner
outer sees: outer
global sees: global
Each scope has its own x. The inner function finds its local x first and uses that. The outer function finds its local x. The global code finds the global x. They coexist without interfering.
Python Quiz
> Write a function that returns the largest value and the count of items from a list. Pick the builtin that finds the maximum and the one that measures the length.
Understanding scope prevents a whole class of subtle bugs where variables unexpectedly share or shadow each other. Local variables created inside a function are destroyed when the function returns, keeping each function call isolated.
Global variables are best used for true constants that do not change during the program, like configuration settings. Passing values through parameters and returning results through return values produces more predictable, testable functions.
When Python searches for a variable name, it looks in local scope first, then enclosing scopes, then global scope, then built-in scope. Understanding this lookup order explains why a local variable with the same name as a global will always shadow the global inside that function.
Type Hints and Documentation
Daily Life
Interviews
Annotate functions with type hints
As programs grow larger and teams work together, it becomes important to document what types of values functions expect and return. Python provides type hints, which are annotations that describe expected types. Type hints do not change how code runs; they are documentation for humans and tools.
Basic Type Hints
Add type hints after parameter names with a colon and the type. Add return type hints after the closing parenthesis with -> followed by the type:
These hints tell readers that calculate_area expects two float numbers and returns a float. greet expects a str and returns a str. is_adult expects an int and returns a bool (True or False).
Common Type Hints
int: Whole numbers like 5, -10, 0
float: Decimal numbers like 3.14, -2.5
str: Text strings like "hello"
bool: Boolean values True or False
None: The special "no value" value
list: A list of items
dict: A dictionary of key-value pairs
Type Hints Are Not Enforced
Python does not enforce type hints at runtime. You can pass a str where the hint says int, and Python will try to run the code. This differs from statically-typed languages like Java or TypeScript. Type hints are purely informational:
1
defdouble(x:int)->int:
2
returnx*2
3
4
print(double(5))
5
print(double("ab"))
6
>>>Output
10
abab
The type hint says int, but Python happily multiplies a str by 2 (which repeats it). Type hints are for documentation and for external tools like type checkers (mypy) that analyze code before you run it.
Why Use Type Hints?
Type hints provide significant benefits for code quality and maintainability, especially in larger codebases and team environments.
IDE support: Code editors provide better autocomplete
Error prevention: Type checkers catch bugs before runtime
Refactoring: Easier to change code with type information
Team communication: Clear contracts between functions
Adopting type hints early builds a habit that pays off as your codebase grows.
TIP
Start using type hints even in personal projects. They make your code self-documenting and help you catch bugs early. Many professional codebases require type hints for all function signatures.
The broader Python ecosystem has invested heavily in type hinting infrastructure.
Common Mistakes
Everyone makes these mistakes when learning functions. Knowing about them helps you recognize and fix them quickly when they happen:
Forgetting to Call Function
Defining a function does not run it. You must call the function with parentheses to execute its code:
•Wrong
def greet():
print("Hello!")
# Function defined but never called
# Nothing prints!
•Correct
def greet():
print("Hello!")
greet() # Call with parentheses
# Prints "Hello!"
Forgetting return Statement
If you want to use a function's result, you must return it. Calculating a value without returning it means the result is lost:
•Wrong
def add(a, b):
result = a + b
# Forgot return!
total = add(3, 5) # total is None
•Correct
def add(a, b):
return a + b
total = add(3, 5) # total is 8
The mistake of forgetting return is very common among beginners. Try fixing the bug below where a function calculates a value but accidentally loses it.
Debug Challenge
> This function calculates the area of a rectangle but prints it instead of returning it. The caller gets None because there is no return statement.
The function prints 15 but result is None because the function never returns the value.
Python executes code from top to bottom. A function must be defined before the line that calls it:
1
# greet() # NameError
2
3
defgreet():
4
print("Hello!")
5
6
greet()
Reference vs Call Confusion
greet (without parentheses) is the function object itself. greet() (with parentheses) calls the function. They are very different:
1
defgreet():
2
return"Hello!"
3
4
print(greet)
5
6
print(greet())
>>>Output
<function greet at 0x...>
Hello!
❯❯❯PUTTING IT ALL TOGETHER
> You are a data analyst at Salesforce building a reusable ETL utility that cleans contact records across ten regional CSV files, each requiring the same standardization logic applied consistently without duplicating code.
def packages the record-cleaning logic once with a descriptive name so every regional file calls the same standardization routine without copy-pasting.
Function parameters accept each regional dataset as an argument, letting the same function body operate on different inputs without hardcoded file paths.
return passes the cleaned record list back to the caller so the result can be written to the output file or passed to the next pipeline stage.
Scope and variable rules ensure counters and temporary strings inside the cleaning function stay isolated and do not accidentally overwrite variables in the outer pipeline script.
KEY TAKEAWAYS
Use def to define functions that package reusable code
Parameters receive values passed as arguments when calling
Default values (param=value) make parameters optional
return sends a value back; without it, the function returns None
Local variables exist only inside their function
Global variables can be read but not modified without the global keyword
Type hints document expected types but are not enforced at runtime
Functions must be defined before the code that calls them
Building reusable blocks of logic
Category
Python
Difficulty
beginner
Duration
37 minutes
Challenges
0 hands-on challenges
Topics covered: Defining Functions, Function Parameters, Return Values, Scope and Variables, Type Hints and Documentation
Functions are reusable blocks of code that perform a specific task. They help you organize your programs and avoid repeating the same code. What is a Function? Why Create Functions? Built-in functions cover common operations, but your programs have unique requirements. You might need to calculate taxes for your specific business rules, format names according to your company standards, or validate data in ways Python cannot predict. Creating your own functions lets you package this custom logic a
Most functions need input data to work with. A function that calculates area needs to know the dimensions. A function that formats names needs to know the name. Parameters are variables that receive values when you call the function. They appear inside the parentheses in the function definition and act as placeholders for the actual values you will provide. Two terms that beginners often mix up are parameters and arguments. They refer to different sides of the same coin. Multiple Parameters Func
Return vs Print: Key Diff Functions Without Return Functions without return values are used for their "side effects" such as printing messages, writing to files, or modifying data. They perform actions rather than computing results. Both patterns are valid; the choice depends on the function's purpose. Return Exits Immediately Each condition checks the age and returns immediately if it matches. The function never runs more code than necessary. When a return is executed, Python skips all remainin
Scope determines where a variable exists and can be accessed. Understanding scope prevents bugs where variables unexpectedly have wrong values or do not exist when you expect them to. Python has two main scopes relevant to functions: local scope (inside a function) and global scope (outside all functions). Local Variables Variables created inside a function are local to that function. They are created when the function starts running and destroyed when the function finishes. They do not exist ou
As programs grow larger and teams work together, it becomes important to document what types of values functions expect and return. Python provides type hints, which are annotations that describe expected types. Type hints do not change how code runs; they are documentation for humans and tools. Basic Type Hints Type Hints Are Not Enforced Why Use Type Hints? Type hints provide significant benefits for code quality and maintainability, especially in larger codebases and team environments. Adopti