Airbnb's booking system must validate dozens of conditions before confirming any reservation, including calendar availability, payment authorization, identity verification, age restrictions, and local legal requirements, and it uses Python's short-circuit evaluation and guard clauses to stop checking the moment any one condition fails. Rather than running every check regardless of outcome, the system exits at the first failure, saving computation and returning a precise error to the user instantly. This approach keeps the booking code flat and readable, with each validation rule standing alone as a clear guard rather than buried inside nested if-else blocks. The patterns you learn in this lesson are exactly how engineers at Airbnb keep complex multi-condition logic maintainable as regulations and business rules continue to evolve.
Guard Clauses
Daily Life
Interviews
Reject bad inputs before they cause bugs
A guard clause is a conditional statement at the beginning of a function or code block that checks for invalid or edge cases and exits early. Instead of nesting your main logic inside an if block, you check for the "bad" cases first and handle them immediately. This keeps your main logic at the top level of indentation.
The term "guard" comes from the idea that these clauses guard the main logic from invalid inputs. They stand at the entrance and turn away anything that should not proceed.
The Problem: Deep Nesting
Consider code that validates multiple conditions before proceeding. Without guard clauses, you end up with deeply nested code that is hard to follow:
Notice how the main logic (processing the user) is buried four levels deep. Each condition pushes the happy path further to the right. This is often called the "arrow anti-pattern" because the code forms an arrow shape.
Refactoring: Guard Clauses
Guard clauses invert the conditions and return early for invalid cases. The main logic stays at the base indentation level:
Each guard clause is independent and easy to understand. You can read them from top to bottom: "If no user, error. If inactive, error. If not verified, error. If no balance, error. Otherwise, process."
The guard clause pattern follows a consistent sequence that keeps your main logic clean and readable:
01
Identify bad input
List every edge case that should prevent normal processing
02
Check and exit early
Each guard tests one condition and returns immediately if it fails
03
Write main logic
After all guards pass, write the happy path at the base indentation
Guard Clauses Everywhere
You can apply the guard clause pattern even when not using functions, by continuing to the next iteration or breaking early:
1
records=[
2
{"id":1,"value":100},
3
{"id":2,"value":None},
4
{"id":3,"value":-50},
5
{"id":4,"value":200},
6
]
7
8
valid_records=[]
9
10
forrecordinrecords:
11
# Guard: skip if value is None
12
ifrecord.get("value")isNone:
13
print("Skipping record",record["id"],"- no value")
Guard clauses work best when you have many conditions to check. If you only have one or two simple conditions, a regular if-else might be clearer.
Fill in the Blank
> A function receives a value that might be None. Pick what the guard clause should do when it detects invalid input.
def process(value):
if value is None:
return value * 2
Guard clauses make the contract of a function explicit. When you list preconditions at the top, any reader immediately knows what the function requires before it will do anything meaningful.
The "return early" style keeps the happy path at the leftmost indentation level. This also makes it easy to add new guards later without restructuring the entire function body.
In loops, guard clauses use continue instead of return to skip invalid records while keeping the loop running. This pattern is common in data pipelines where you want to process as many records as possible and quarantine the bad ones.
Chained Comparisons
Daily Life
Interviews
Validate ranges with readable expressions
Python allows you to chain comparison operators in a way that reads naturally. Instead of writing x > 5 and x < 10, you can write 5 < x < 10. This matches how you would express ranges in mathematics and makes your code more readable.
Basic Chained Comparisons
You can chain any comparison operators together. Python evaluates them left to right, and all comparisons must be true for the entire expression to be true:
1
x=15
2
3
# Instead of: x > 10 and x < 20
4
if10<x<20:
5
print(x,"is between 10 and 20 (exclusive)")
6
7
# Instead of: x >= 10 and x <= 20
8
if10<=x<=20:
9
print(x,"is between 10 and 20 (inclusive)")
10
11
# Chain more than two comparisons
12
y=5
13
if0<y<10<x<20:
14
print("Both y and x are in their expected ranges")
15
16
# Works with variables on all sides
17
low=10
18
high=20
19
iflow<x<high:
20
print(x,"is between",low,"and",high)
>>>Output
15 is between 10 and 20 (exclusive)
15 is between 10 and 20 (inclusive)
Both y and x are in their expected ranges
15 is between 10 and 20
The expression 10 < x < 20 is equivalent to 10 < x and x < 20, but shorter and more readable. Python evaluates x only once.
Chained comparisons are especially useful in data validation, where you frequently need to confirm values fall within acceptable ranges:
a < x < ba <= x <= ba == b == ca < b < c
a < x < b
Exclusive
Value between, not equal
a <= x <= b
Inclusive
Value between or on edge
a == b == c
All equal
Every value is identical
a < b < c
Sorted order
Values are ascending left
Practical Range Checking
Chained comparisons are perfect for validating that values fall within expected ranges:
1
defvalidate_percentage(value):
2
"""Validate percentage."""
3
if0<=value<=100:
4
return"Valid percentage"
5
return"Invalid: must be 0-100"
6
7
defvalidate_age(age):
8
"""Validate age."""
9
if0<age<150:
10
return"Valid age"
11
return"Invalid age"
12
13
defcategorize_temperature(temp):
14
"""Categorize temperature."""
15
iftemp<32:
16
return"freezing"
17
elif32<=temp<50:
18
return"cold"
19
elif50<=temp<70:
20
return"cool"
21
elif70<=temp<85:
22
return"warm"
23
else:
24
return"hot"
25
26
print(validate_percentage(75))
27
print(validate_percentage(150))
28
print(categorize_temperature(65))
>>>Output
Valid percentage
Invalid: must be 0-100
cool
Chaining Comparisons
You can also chain equality operators, which is useful for checking if multiple values are equal:
1
a=5
2
b=5
3
c=5
4
5
# Check if all three are equal
6
ifa==b==c:
7
print("All three values are equal")
8
9
# Check if sorted in order
10
x=1
11
y=2
12
z=3
13
14
ifx<y<z:
15
print("Values are in ascending order")
16
17
ifx<=y<=z:
18
print("Values are in non-descending order")
19
20
# Mix of operators
21
score=85
22
if80<=score<90:
23
print("Grade: B")
>>>Output
All three values are equal
Values are in ascending order
Values are in non-descending order
Grade: B
Debug Challenge
> This percentage validator has a condition that can never be True. Fix the logic so invalid values are caught correctly.
Logic error: the elif condition can never be True. A number cannot be both less than 0 AND greater than or equal to 100.
Chained comparisons are especially easy to get wrong when combining inequality operators. The safest approach is to read the chain aloud as a mathematical expression: "Is value at least 0 and at most 100?" maps directly to "0 <= value <= 100".
When the valid range is clear, an else clause handles everything outside it without needing a second chained comparison. Trying to express the invalid range as its own chain often leads to a condition that can never be satisfied.
Chained comparisons only work in one direction. Python evaluates a < x < b left to right, so a < x and x < b. If you reverse the direction inconsistently, like a < x and x >= b combined, you may accidentally overlap or leave gaps in your range logic.
Pattern Matching with match-case
Daily Life
Interviews
Dispatch on value patterns cleanly
Python 3.10 introduced match-case, also known as structural pattern matching. It provides a cleaner way to handle multiple conditions compared to long if-elif-else chains. The match statement compares a value against several patterns and executes the code for the first matching pattern.
Basic match-case Syntax
The match keyword is followed by the value to match, then case clauses define patterns to match against:
The | operator matches multiple patterns (like "or"). The underscore _ is a wildcard that matches anything, like a default case.
Matching Values
Match-case is excellent for handling discrete values like status codes, commands, or types:
1
defhandle_http_status(code):
2
matchcode:
3
case200:
4
return"OK"
5
case201:
6
return"Created"
7
case400:
8
return"Bad Request"
9
case401:
10
return"Unauthorized"
11
case403:
12
return"Forbidden"
13
case404:
14
return"Not Found"
15
case500:
16
return"Internal Server Error"
17
case_:
18
return"Unknown Status"
19
20
print(handle_http_status(200))
21
print(handle_http_status(404))
22
print(handle_http_status(418))
>>>Output
OK
Not Found
Unknown Status
Matching with Guards
You can add an if clause after a pattern to add additional conditions. This is called a guard:
1
defcategorize_number(n):
2
matchn:
3
case0:
4
return"zero"
5
casenifn<0:
6
return"negative"
7
casenifn%2==0:
8
return"positive even"
9
case_:
10
return"positive odd"
11
12
print(categorize_number(0))
13
print(categorize_number(-5))
14
print(categorize_number(8))
15
print(categorize_number(7))
>>>Output
zero
negative
positive even
positive odd
Guards let you add conditions that go beyond simple value matching. The pattern variable (n in this case) captures the matched value for use in the guard and the block.
Match-case supports several pattern types. Each serves a different matching strategy:
LiteralWildcardCaptureGuardOR |
Literal
Exact values
Matches a specific number
Wildcard
Catch-all _
Default if nothing else
Capture
Bind variable
Stores value for later use
Guard
if condition
Adds extra boolean check
OR |
Alternatives
Matches any of the options
Matching Sequences
Match-case can destructure sequences like lists and tuples, matching both structure and content. This is powerful for parsing data structures where the shape of the data determines how to handle it. Pattern matching automatically extracts values from the matched structure and binds them to variables:
1
defdescribe_point(point):
2
matchpoint:
3
case(0,0):
4
return"origin"
5
case(x,0):
6
return"on x-axis at "+str(x)
7
case(0,y):
8
return"on y-axis at "+str(y)
9
case(x,y):
10
return"point at ("+str(x)+", "+str(y)+")"
11
case_:
12
return"not a point"
13
14
print(describe_point((0,0)))
15
print(describe_point((5,0)))
16
print(describe_point((0,3)))
17
print(describe_point((2,4)))
>>>Output
origin
on x-axis at 5
on y-axis at 3
point at (2, 4)
•Use match-case When
Many discrete values to handle
Pattern matching on structure
Command/event dispatching
Cleaner than long elif chains
•Use if-elif When
Complex boolean conditions
Only 2-3 conditions
Range-based comparisons
Python version < 3.10
Fill in the Blank
> A command classifier uses match-case. Pick the correct pattern for the default case that catches unrecognized commands.
def classify(command):
match command:
case "start":
return "Starting"
case "stop":
return "Stopping"
case :
return "Unknown"
print(classify("restart"))
The wildcard pattern _ is the match-case equivalent of the else clause. Always include it to handle unexpected values gracefully rather than silently returning None when no case matches.
The | operator in match-case lets you group values that share the same handling. This is cleaner than a long elif chain with many == comparisons when several inputs lead to the same outcome.
Match-case was introduced in Python 3.10, so it is not available in older environments. In data pipelines running on legacy infrastructure, if-elif chains remain the appropriate alternative.
Python Quiz
> Classify a day as weekend or weekday using pattern matching. Pick the keyword that starts pattern matching, and the operator that combines alternative patterns in a single case.
Match-case reads naturally when each case corresponds to a meaningful category. The structure forces you to be explicit about every value the code handles, which makes exhaustive handling of a set of inputs clearer than a scattered chain of elif statements.
Guard clauses inside match cases (using "if" after the pattern) let you add conditions that go beyond simple value equality. This keeps complex logic organized inside a single match block rather than spreading it across nested if statements.
When you find yourself writing a long elif chain that compares one variable against many literal values, that is usually a signal to consider replacing it with match-case for improved readability and intent clarity.
Conditional Assignment
Daily Life
Interviews
Assign values based on conditions inline
Conditional assignment lets you assign a value to a variable based on a condition, all in a single line. This is also called a ternary expression or conditional expression. It makes your code more concise when you need to choose between two values.
Ternary Expression Syntax
The syntax is: value_if_true if condition else value_if_false. The condition goes in the middle:
1
age=20
2
3
# Instead of:
4
# if age >= 18:
5
# status = "adult"
6
# else:
7
# status = "minor"
8
9
# Use conditional expression:
10
status="adult"ifage>=18else"minor"
11
print("Status:",status)
12
13
# More examples
14
score=85
15
grade="pass"ifscore>=60else"fail"
16
print("Grade:",grade)
17
18
temperature=25
19
weather="warm"iftemperature>20else"cool"
20
print("Weather:",weather)
>>>Output
Status: adult
Grade: pass
Weather: warm
The conditional expression evaluates the condition first. If True, it returns the value before if. If False, it returns the value after else.
Using in Function Calls
Conditional expressions are especially useful when passing arguments to functions or building strings:
1
items=["apple","banana","cherry"]
2
empty_list=[]
3
4
# Use in function calls
5
print("Items:"ifitemselse"No items")
6
print("Items:"ifempty_listelse"No items")
7
8
# Building messages
9
count=5
10
message=str(count)+" item"+("s"ifcount!=1else"")
11
print(message)
12
13
count=1
14
message=str(count)+" item"+("s"ifcount!=1else"")
15
print(message)
16
17
# Choose which function result to use
18
x=-5
19
result=abs(x)ifx<0elsex
20
print("Result:",result)
>>>Output
Items:
No items
5 items
1 item
Result: 5
Nested Conditionals
You can nest conditional expressions, but this quickly becomes hard to read. Use with caution:
# Better: use regular if-elif for more than 2 choices
8
# or split into multiple lines for readability:
9
grade=(
10
"A"ifscore>=90else
11
"B"ifscore>=80else
12
"C"ifscore>=70else
13
"F"
14
)
15
print("Grade (formatted):",grade)
>>>Output
Grade: C
Grade (formatted): C
TIP
Limit conditional expressions to simple two-way choices. If you have more than two options or complex conditions, use regular if-elif-else for clarity.
Ternary expressions are powerful but easy to misuse. Follow these guidelines to keep your conditional assignments readable:
✓Do
Use for simple two-way value selection
Keep both values short and clear
Split long expressions across lines
Use parentheses for nested ternaries
✗Don't
Chain more than two ternaries
Put side effects inside ternaries
Use when logic is complex
Sacrifice readability for brevity
Default Values with or
A common pattern uses or to provide default values. If the left side is falsy (None, empty, 0, False), the right side is used:
1
# Get name or use default
2
user_name=None
3
display_name=user_nameor"Anonymous"
4
print(display_name)
5
6
# Get from dict with fallback
7
config={"timeout":30}
8
timeout=config.get("timeout")or60
9
retries=config.get("retries")or3
10
11
print("Timeout:",timeout)
12
print("Retries:",retries)
13
14
# Careful with 0 - it's falsy!
15
value=0
16
result=valueor100# This returns 100, not 0!
17
print("Result:",result)
18
19
# Use "is not None" for values that might be 0
20
value=0
21
result=valueifvalueisnotNoneelse100
22
print("Safe result:",result)
>>>Output
Anonymous
Timeout: 30
Retries: 3
Result: 100
Safe result: 0
Fill in the Blank
> The value is 0, which is valid but falsy. Pick the ternary condition that correctly preserves 0 instead of replacing it.
value = 0
result = value if else 100
print(result)
The or default pattern is convenient but dangerous when 0, False, or empty string are valid values. Switching to an explicit is not None check makes the intent unambiguous and prevents hard-to-find data loss bugs.
Ternary expressions work best for straightforward two-option assignments. When the condition or either value is long, split the expression across lines with parentheses to preserve readability.
Conditional assignment keeps related logic together on one line rather than spreading a simple value choice across four lines of if-else. This makes code scanning faster when you are reviewing a function's outputs at a glance.
Edge Case Handling
Daily Life
Interviews
Handle None, empty, and boundary inputs
Edge cases are inputs or situations at the boundaries of what your code handles: empty collections, zero values, negative numbers, None values, and extreme values. Robust code anticipates and handles these cases explicitly. Failure to handle edge cases is one of the most common sources of bugs.
Common Edge Cases
Here are the most common edge cases you should always consider:
None / null values
Missing or unset data that causes crashes when accessed
Empty collections
Lists, strings, and dicts with zero elements
Zero values
Division by zero, zero-length strings, index zero edge cases
Negative numbers
When only positive values are expected by the logic
Boundary values
First and last elements, min and max limits
Invalid types
A string where an integer was expected, or vice versa
Handling None Values
None represents the absence of a value. Always check for None before using a value that might not exist:
1
defget_length(items):
2
# Guard against None
3
ifitemsisNone:
4
return0
5
returnlen(items)
6
7
print(get_length([1,2,3]))
8
print(get_length(None))
9
10
# Using "is" for None
11
value=None
12
13
# Check for None
14
ifvalueisNone:
15
print("Value is None")
16
17
ifvalue==None:
18
print("Value equals None")
19
20
# if not value: # Risky
>>>Output
3
0
Value is None
Value equals None
Always use is None rather than == None. The is operator checks identity, which is what you want for None.
Handling Empty Collections
Empty lists, strings, and dicts are falsy in Python, but you should often handle them explicitly:
1
defcalculate_average(numbers):
2
# Guard against empty list
3
ifnotnumbers:
4
return0
5
6
returnsum(numbers)/len(numbers)
7
8
print(calculate_average([10,20,30]))
9
print(calculate_average([]))
10
11
defget_first(items):
12
# Guard against empty
13
ifnotitems:
14
returnNone
15
returnitems[0]
16
17
print(get_first(["a","b","c"]))
18
print(get_first([]))
19
20
# Check before accessing
21
data={"users":[]}
22
ifdata.get("users"):
23
print("Has users")
24
else:
25
print("No users")
>>>Output
20.0
0
a
None
No users
Python Quiz
> Guard against division by zero by checking the denominator first. Pick the comparison that detects zero, and the division operator that returns a float.
Division by zero and index-out-of-range errors are among the most common runtime crashes in data pipelines. A single guard clause at the start of a function is all it takes to make these errors safe and explicit.
None checks deserve special attention. Unlike other edge cases, None can appear anywhere a value is expected: from missing dictionary keys, unset function parameters, and failed lookups. Checking is None before use is a habit that pays off in reliability.
Combining None and empty-collection guards in the right order matters. If you call len(items) before checking items is None, you will get a TypeError before your None guard even has a chance to run.
Debug Challenge
> This function crashes on empty lists and None inputs. Add guard clauses so it handles edge cases safely.
IndexError: list index out of range (for empty list) and TypeError (for None)
Pay special attention to boundary values: the first and last elements, minimum and maximum values:
1
defsafe_divide(a,b):
2
# Guard against division by zero
3
ifb==0:
4
returnNone
5
returna/b
6
7
print(safe_divide(10,2))
8
print(safe_divide(10,0))
9
10
defget_element(items,index):
11
# Guard against out-of-bounds
12
ifindex<0orindex>=len(items):
13
returnNone
14
returnitems[index]
15
16
data=[10,20,30]
17
print(get_element(data,1))
18
print(get_element(data,10))
19
print(get_element(data,-5))
>>>Output
5.0
None
20
None
None
Defensive Programming
A defensive programming approach handles edge cases at the start of every function:
1
defprocess_records(records):
2
"""Process a list of records safely."""
3
# 1. Handle None
4
ifrecordsisNone:
5
print("Error: records is None")
6
return[]
7
8
# 2. Handle wrong type
9
ifnotisinstance(records,list):
10
print("Error: records must be a list")
11
return[]
12
13
# 3. Handle empty
14
iflen(records)==0:
15
print("Warning: no records to process")
16
return[]
17
18
# 4. Main logic - records is non-empty
19
processed=[]
20
forrecordinrecords:
21
ifrecordisnotNone:
22
processed.append(str(record).upper())
23
24
returnprocessed
25
26
print(process_records(["a","b","c"]))
27
print(process_records(None))
28
print(process_records("not a list"))
29
print(process_records([]))
>>>Output
['A', 'B', 'C']
Error: records is None
[]
Error: records must be a list
[]
Warning: no records to process
[]
Putting It All Together
Let us combine the patterns from this lesson into a realistic example. This data validation function uses guard clauses, chained comparisons, conditional expressions, and proper edge case handling:
1
defvalidate_user_record(record):
2
"""Validate a user record with comprehensive checks."""
This function demonstrates several intermediate control flow patterns working together. Guard clauses at the top handle invalid inputs immediately. Chained comparisons validate the age range naturally. A conditional expression assigns the user category concisely. The function returns early for each error case, keeping the success path at the end. This structure makes the code self-documenting and easy to maintain as requirements evolve.
Data Processing Flow
Here is another practical example showing how to filter and transform a dataset using the patterns from this lesson. This kind of batch processing is common in ETL pipelines and data validation systems:
This data processing loop uses guard clauses with continue to skip invalid records, keeping the main processing logic clean and readable. Each guard clause handles one type of invalid data, making it easy to understand and modify the validation rules. The pattern scales well to complex pipelines with many validation steps, as each check remains independent and testable.
Chained Comparisons
Here is a more comprehensive example showing how chained comparisons simplify range-based validation and categorization in data pipelines:
1
defanalyze_metrics(metrics):
2
"""Analyze performance metrics with range-based categorization."""
The chained comparisons make the range logic immediately clear. Reading "50 <= value < 80" is more intuitive than "value >= 50 and value < 80". This clarity is especially valuable when you return to code after months and need to quickly understand the boundaries. Python evaluates the expression efficiently, checking each comparison in sequence and stopping as soon as one fails.
Guard Clauses: API Handlers
Guard clauses are particularly valuable in functions that handle API requests or user input, where many things can go wrong. Here is a realistic example of a user registration handler:
1
defregister_user(request):
2
"""Handle user registration with comprehensive validation."""
3
# Guard: request must exist
4
ifrequestisNone:
5
return{"error":"No request data","code":400}
6
7
# Guard: required fields
8
if"email"notinrequest:
9
return{"error":"Email is required","code":400}
10
11
if"password"notinrequest:
12
return{"error":"Password is required","code":400}
13
14
# Guard: email format
15
email=request["email"]
16
if"@"notinemailor"."notinemail.split("@")[-1]:
17
return{"error":"Invalid email format","code":400}
18
19
# Guard: password strength
20
password=request["password"]
21
iflen(password)<8:
22
return{"error":"Password must be at least 8 characters","code":400}
23
24
ifpassword.lower()==password:
25
return{"error":"Password must contain uppercase letters","code":400}
26
27
# Guard: check if email already exists (simulated)
Each guard clause handles one specific validation failure. The function reads from top to bottom like a checklist: "If no request, error. If no email, error. If email invalid, error." This structure makes it easy to add new validations or modify existing ones without affecting other parts of the function. The pattern also makes testing straightforward, as each guard clause can be tested independently with inputs designed to trigger it.
Well-structured control flow makes code easier to read, test, and maintain.
❯❯❯PUTTING IT ALL TOGETHER
> You are a data engineer at Adyen building a transaction routing script that directs payments to different processing paths based on currency, amount, and risk score. The script must reject invalid inputs early, validate numeric bounds, dispatch by currency type, assign fee tiers conditionally, and handle missing or zero-value fields without crashing.
Guard clauses return early when currency is None or amount <= 0 so invalid transactions never reach the routing logic below.
Chained comparisons like 0<amount<=10000 validate that a transaction amount falls within an accepted processing band in one expression.
match-case dispatches each currency string to its corresponding processor path, with the wildcard _ case catching any unsupported currency.
Conditional assignment sets fee_tier="premium"ifrisk_score>80else"standard" in one line, and edge case handling guards against a None risk_score with isNone.
KEY TAKEAWAYS
Guard clauses exit early for invalid cases, keeping main logic at base indentation
Chained comparisons like a<x<b are more readable than using "and" for range checks
match-case provides cleaner multi-way branching than long elif chains (Python 3.10+)
Use _ as the wildcard/default case in match statements
Conditional expressions xifconditionelsey assign values based on conditions in one line
A guard clause is a conditional statement at the beginning of a function or code block that checks for invalid or edge cases and exits early. Instead of nesting your main logic inside an if block, you check for the "bad" cases first and handle them immediately. This keeps your main logic at the top level of indentation. The term "guard" comes from the idea that these clauses guard the main logic from invalid inputs. They stand at the entrance and turn away anything that should not proceed. The P
Basic Chained Comparisons You can chain any comparison operators together. Python evaluates them left to right, and all comparisons must be true for the entire expression to be true: Chained comparisons are especially useful in data validation, where you frequently need to confirm values fall within acceptable ranges: Practical Range Checking Chained comparisons are perfect for validating that values fall within expected ranges: Chaining Comparisons You can also chain equality operators, which i
Basic match-case Syntax Matching Values Match-case is excellent for handling discrete values like status codes, commands, or types: Matching with Guards Guards let you add conditions that go beyond simple value matching. The pattern variable (n in this case) captures the matched value for use in the guard and the block. Match-case supports several pattern types. Each serves a different matching strategy: Matching Sequences Match-case can destructure sequences like lists and tuples, matching both
Conditional assignment lets you assign a value to a variable based on a condition, all in a single line. This is also called a ternary expression or conditional expression. It makes your code more concise when you need to choose between two values. Ternary Expression Syntax Using in Function Calls Conditional expressions are especially useful when passing arguments to functions or building strings: Nested Conditionals You can nest conditional expressions, but this quickly becomes hard to read. U
Common Edge Cases Here are the most common edge cases you should always consider: Handling None Values Handling Empty Collections Empty lists, strings, and dicts are falsy in Python, but you should often handle them explicitly: Division by zero and index-out-of-range errors are among the most common runtime crashes in data pipelines. A single guard clause at the start of a function is all it takes to make these errors safe and explicit. Boundary Conditions Pay special attention to boundary value