Introduction to Conditionals
Conditional statements are the foundation of decision-making in programming. They allow your program to execute different code blocks based on whether certain conditions are true or false. Think of conditionals as the "brain" of your program - they let it react differently to different situations, just like how you make decisions in real life.
Every meaningful program you have ever used relies heavily on conditionals. When you log into a website, conditionals check if your password is correct. When you play a video game, conditionals determine if you won or lost. When you use a calculator app, conditionals handle different operations based on which button you press. Without conditionals, programs would be completely linear - doing the exact same thing every single time, regardless of input or circumstances.
In this comprehensive lesson, we will explore every aspect of conditional statements in Python, starting from the most basic concepts and building up to advanced pattern matching. By the end, you will be able to write programs that make intelligent decisions, validate user input, control access to features, and handle complex business logic with confidence.
What are Conditional Statements?
Conditional statements are programming constructs that let your program make decisions. They work like a fork in the road - based on a condition (a yes/no question), your program chooses which path to take. If the condition is True (yes), one block of code runs. If it is False (no), a different block runs or the code is skipped entirely.
In simple terms: Conditionals let your program ask questions and act differently based on the answers. Without them, programs would do the exact same thing every single time they run, which would be pretty useless!
Real-World Analogy: Think of a traffic light. If the light is green, you go. If it is yellow, you slow down. If it is red, you stop. Your brain makes these decisions automatically based on the condition (light color). Conditionals work the same way in code - they check a condition and decide what to do next!
Another Way to Think About It: Imagine you are writing instructions for a robot. "Go to the kitchen. If there are dirty dishes, wash them. Otherwise, make coffee." That "if" and "otherwise" is exactly what conditionals do in programming!
Why Do We Need Conditionals?
Imagine if every program did the exact same thing every time you ran it. Your calculator would always show "5" no matter what numbers you entered. Your video game character would always walk right, even when you pressed left. That would be pretty useless, right?
Conditionals give programs the ability to think and react. They let your program respond differently based on what is happening - what the user typed, what data was received, what time it is, or any other condition you can imagine. This is what makes software actually useful!
Consider a simple example: an ATM machine. When you insert your card, the machine does not just dispense money immediately. It first checks if your card is valid, then verifies your PIN, then confirms you have sufficient funds, and only then does it dispense the requested amount. Each of these checks is a conditional - a decision point where the program chooses what to do next based on the current situation.
Here are some common scenarios where conditionals are absolutely essential:
User Input Validation
Check if user input meets requirements before processing - like verifying age for restricted content
Access Control
Determine user permissions - admins see different options than regular users
Business Logic
Apply different rules - tax calculations, discounts, pricing tiers based on conditions
Boolean Values: The Foundation
Before we dive into writing conditionals, we need to understand boolean values. Do not worry - this is simpler than it sounds!
A boolean is just a fancy word for something that can only be one of two things: True or False. That is it! Think of it like a light switch - it is either ON (True) or OFF (False). There is no "maybe" or "sort of" - just yes or no, true or false.
Every condition you write in Python will ultimately become either True or False. When you ask Python "Is 5 greater than 3?", it answers True. When you ask "Is 10 equal to 20?", it answers False. These True/False answers are what conditionals use to decide which code to run.
The boolean data type is named after George Boole, a 19th-century mathematician who developed Boolean algebra - the mathematical foundation of digital logic. In Python, boolean values are capitalized (True and False), unlike some other languages that use lowercase. This is important to remember because true (lowercase) will cause an error in Python!
Boolean values can come from three main sources:
- Direct assignment: You can simply write
is_active = Trueoris_logged_in = False - Comparison operations: Expressions like
age > 18orname == "Alice"produce True or False - Function returns: Some functions return boolean values, like
"hello".startswith("h")returnsTrue
Let us see some examples:
# Boolean values in Python
is_raining = True
is_sunny = False
# Conditions evaluate to boolean values
age = 20
print(age >= 18) # True (age is greater than or equal to 18)
print(age == 25) # False (age is not equal to 25)
# Variables can store condition results
is_adult = age >= 18
print(is_adult) # True
print(type(is_adult)) # <class 'bool'>
Let us break down what is happening in this code:
- Lines 1-2: We directly assign boolean values to variables. This is useful for flags and settings.
- Lines 5-6: Comparison expressions produce boolean results. The expression
age >= 18asks "is 20 greater than or equal to 18?" and Python answersTrue. - Lines 8-10: We store the result of a comparison in a variable. This is the "explaining variable" technique - instead of writing the comparison everywhere, we give it a meaningful name.
In the code above, we see that comparison expressions like age >= 18 produce boolean values. The result True or False can be stored in variables and used later in conditional statements. This is a powerful technique for making your code more readable.
Storing boolean results in descriptively named variables is a best practice called "explaining variables" or "self-documenting code." Instead of writing if age >= 18 and has_id and not is_banned:, you could write:
# Using explaining variables for clarity
is_adult = age >= 18
has_valid_id = has_id
is_allowed = not is_banned
if is_adult and has_valid_id and is_allowed:
print("Access granted")
This makes your code much easier to read and debug, especially when conditions become complex.
Truthy and Falsy Values
Here is something that might surprise you: in Python, everything can be treated as True or False, not just actual boolean values! This is one of Python's most useful (and sometimes confusing) features for beginners.
Think of it this way: Python considers some values to be "empty" or "nothing" - these are called falsy because they act like False in a condition. Everything else is considered to have "something" in it - these are called truthy because they act like True.
The simple rule: Empty things and zero are falsy. Everything else is truthy.
This concept might seem strange at first - how can a number or a string be "true" or "false"? The idea is that Python associates emptiness, nothingness, and zero with False, while anything with content or value is associated with True. This design choice makes conditionals more intuitive and allows for cleaner code.
Here is the complete list of falsy values in Python - memorize these, as everything else is truthy:
False- the boolean False itselfNone- Python's null/nothing value0- zero (integer)0.0- zero (float)0j- zero (complex number)""- empty string[]- empty list()- empty tuple{}- empty dictionaryset()- empty setrange(0)- empty range
# Falsy values in Python (evaluate to False)
print(bool(False)) # False - the boolean False itself
print(bool(0)) # False - zero (int)
print(bool(0.0)) # False - zero (float)
print(bool("")) # False - empty string
print(bool([])) # False - empty list
print(bool({})) # False - empty dictionary
print(bool(None)) # False - None value
# Truthy values (evaluate to True)
print(bool(True)) # True - the boolean True
print(bool(1)) # True - non-zero number
print(bool(-5)) # True - negative numbers are truthy!
print(bool("Hello")) # True - non-empty string
print(bool([1, 2])) # True - non-empty list
print(bool(" ")) # True - string with space (not empty!)
This concept is incredibly useful. For example, you can check if a list has items by simply using if my_list: instead of if len(my_list) > 0:. The empty list evaluates to False, so the condition fails when the list is empty.
Here are some practical examples of using truthy/falsy values to write cleaner code:
# Instead of this verbose check:
if len(user_input) > 0:
process(user_input)
# Write this (more Pythonic):
if user_input:
process(user_input)
# Instead of checking for None explicitly:
if result != None:
print(result)
# Write this (but use 'is' for None checks):
if result is not None:
print(result)
# Checking if a dictionary has entries:
if settings: # True if settings has any key-value pairs
apply_settings(settings)
if name != "":, you can simply write if name:. This is more Pythonic and easier to read!
Practice: Boolean Basics
Task: Without running the code, predict what each expression will evaluate to (True or False), then verify by running. Pay attention to comparison operators and remember that Python string comparisons are case-sensitive! This mental exercise helps you debug conditionals faster.
temperature = 25
price = 99.99
name = "Alice"
# Predict the output of each:
print(temperature > 20)
print(price == 100)
print(name == "alice")
print(len(name) >= 5)
Show Solution
temperature = 25
price = 99.99
name = "Alice"
print(temperature > 20) # True (25 > 20)
print(price == 100) # False (99.99 is not equal to 100)
print(name == "alice") # False (case-sensitive: "Alice" != "alice")
print(len(name) >= 5) # True (len("Alice") is 5, and 5 >= 5)
Task: Given a list of mixed values, write code to print whether each value is truthy or falsy. This exercise helps you internalize which values Python considers "empty" or "nothing" (falsy) versus values with actual content (truthy). Understanding this is crucial for writing elegant Python conditionals.
values = [0, 1, "", "hello", [], [1, 2], None, True, False, -1]
# For each value, print: "value is truthy" or "value is falsy"
Show Solution
values = [0, 1, "", "hello", [], [1, 2], None, True, False, -1]
for value in values:
if value:
print(f"{repr(value)} is truthy")
else:
print(f"{repr(value)} is falsy")
# Output:
# 0 is falsy
# 1 is truthy
# '' is falsy
# 'hello' is truthy
# [] is falsy
# [1, 2] is truthy
# None is falsy
# True is truthy
# False is falsy
# -1 is truthy
Task: Write a function analyze_number(n) that returns a dictionary with boolean values for: is_positive, is_even, is_two_digit, and is_perfect_square. This combines multiple boolean concepts: comparisons, modulo for even/odd, range checking, and mathematical operations. A perfect square is a number whose square root is an integer (like 4, 9, 16, 25).
Show Solution
import math
def analyze_number(n):
"""Analyze various properties of a number."""
return {
"is_positive": n > 0,
"is_even": n % 2 == 0,
"is_two_digit": 10 <= abs(n) <= 99,
"is_perfect_square": n >= 0 and int(math.sqrt(n)) ** 2 == n
}
# Test the function
print(analyze_number(16))
# {'is_positive': True, 'is_even': True, 'is_two_digit': True, 'is_perfect_square': True}
print(analyze_number(25))
# {'is_positive': True, 'is_even': False, 'is_two_digit': True, 'is_perfect_square': True}
print(analyze_number(-7))
# {'is_positive': False, 'is_even': False, 'is_two_digit': False, 'is_perfect_square': False}
if, elif, and else Statements
Now that you understand boolean values, let us learn the actual keywords that make conditionals work: if, elif, and else. These are the building blocks you will use thousands of times in your programming journey!
The Three Conditional Keywords
Python uses three keywords for conditional logic: if starts the decision, elif (else-if) adds more options, and else catches everything remaining. Together, they create a decision tree where exactly one block of code executes based on which condition is true first.
Think of it like this: "If it is raining, take an umbrella. Else if it is sunny, wear sunglasses. Else (for any other weather), just go outside." Python evaluates each condition in order and stops at the first true one!
if
"If this is true, do this thing." Starts every conditional. Required.
elif
"Otherwise, if THIS is true instead..." Short for "else if". Optional, can have many.
else
"If nothing above was true, do this." The catch-all safety net. Optional.
These three keywords work together like a flowchart with decision points. You start by checking the if condition. If it fails, you check each elif condition in order. If they all fail, the else block runs as a catch-all. Only ONE of these blocks will ever run - as soon as Python finds a true condition, it runs that block and skips all the rest.
{} like other languages. This makes code visually clean but means whitespace is part of the syntax!
The Basic if Statement
Let us start with the simplest conditional: the if statement. It is exactly what it sounds like - "if something is true, do this."
- Write the word
if - Write a condition (True or False)
- Put a colon
:at the end - Indent the code block with 4 spaces
if condition:
# This code runs only if
# condition is True
do_something()
If the condition is True, Python runs the indented code. If the condition is False, Python skips it completely and moves on. Let us see a real example:
# Basic if statement syntax
age = 20
if age >= 18:
print("You are an adult")
print("You can vote in elections")
print("This always prints, regardless of age")
# Output:
# You are an adult
# You can vote in elections
# This always prints, regardless of age
Step by Step Breakdown
Follow the execution flow of this if statement
Set Variable
We assign the value
age = 20
Check Condition
Python evaluates:
age >= 18
Execute Block
Condition is True!
Continue
Outside the if block
age to 15 in your mind. The condition 15 >= 18 would be False, so Python would skip the indented block entirely, and only the last line would print!Notice the colon (:) after the condition and the indentation of the code block. Python uses indentation (typically 4 spaces) to define code blocks, unlike languages that use curly braces. The indented lines belong to the if statement and only execute when the condition is true.
: after the condition is one of the most frequent syntax errors for beginners. Python will raise a SyntaxError if you forget it. Always remember: condition followed by colon, then indented block!
The standard convention in Python is to use 4 spaces for each level of indentation. While you can technically use tabs or a different number of spaces, mixing tabs and spaces will cause errors, and the Python community strongly recommends 4 spaces. Most code editors can be configured to insert 4 spaces when you press the Tab key.
The if-else Statement
What is if-else?
The if-else statement is like standing at a fork in the road. You MUST choose one path - you cannot go both ways or stand still. Based on a condition, Python will execute exactly one of two code blocks - never both, never neither.
Key Insight: Use if-else when you need to handle both outcomes of a decision - what to do when something is true AND what to do when it's false.
if-else like a light switch:
- If the switch is ON → the light shines
- Else (switch is OFF) → the room stays dark
if-else guarantees one of two actions will happen!
Syntax Structure
# Basic if-else syntax structure
if condition:
# This block runs when condition is True
# Can have multiple lines
# All lines must be indented
else:
# This block runs when condition is False
# Also can have multiple lines
# Also must be indented
The Two Paths
Every if-else creates exactly two possible execution paths
if block
When does this run?
This block executes only when Python evaluates your condition and finds it to be True. The moment the condition passes, Python enters this block.
What happens inside?
All indented code under the if statement runs line by line. You can have as many lines as needed - print statements, calculations, function calls, even nested conditions!
Real-World Example
Scenario: Checking if it's raining
If is_raining == True → Take umbrella, wear raincoat, drive carefully
else block
When does this run?
This block is the fallback option. It executes when Python checks the if condition and finds it to be False. Think of it as the "otherwise" action.
What happens inside?
Just like the if block, all indented code under else runs sequentially. The else keyword doesn't need a condition - it automatically catches everything the if missed.
Real-World Example
Scenario: Checking if it's raining
Else (not raining) → Leave umbrella home, enjoy the sunshine!
Practical Example
# if-else statement - Weather Decision
temperature = 15
if temperature >= 25:
print("It is warm outside")
print("Wear light clothes")
else:
print("It is cool outside")
print("Consider wearing a jacket")
# Output (since temperature is 15):
# It is cool outside
# Consider wearing a jacket
How This Code Works
Follow the execution flow step by step
Check Condition
Python reads the if statement and evaluates the condition:
temperature >= 25
15 is NOT >= 25, so condition fails
Skip if Block
Since condition is False, Python completely ignores the if block:
print("It is warm outside")
print("Wear light clothes")
These lines never execute
Run else Block
Python jumps to the else block and executes its code:
print("It is cool outside")
print("Consider wearing a jacket")
Output prints to console
It is cool outside
Consider wearing a jacket
The else block catches all cases where the if condition fails. Think of it as a safety net - if the main condition is not met, the program falls through to the else block.
- The
elsekeyword does NOT have a condition - it automatically catches everything theifmissed - The
elseblock is optional - you can useifalone withoutelse - When you include
else, exactly one of the two blocks will always execute - Both
ifandelsemust end with a colon:
Example: Random Number Decision
# Example showing if-else guarantees one block executes
import random
number = random.randint(1, 10)
if number > 5:
print(f"{number} is greater than 5")
else:
print(f"{number} is 5 or less")
# One of these messages will ALWAYS print, never both, never neither
Mental Model for Beginners
Read if-else in plain English: "If this condition is true, do this thing. Otherwise (else), do that other thing." The word "else" in Python means exactly what it means in everyday language - it's the alternative option!
The if-elif-else Chain
When you have multiple conditions to check, use elif (short for "else if"). This lets you create a chain of conditions where Python checks each one in order and executes the first block whose condition is True.
elif Chain Execution Flow
Python checks conditions from top to bottom. The moment it finds a True condition, it executes that block and skips all remaining elif and else blocks. This is called "early exit" or "short-circuit evaluation."
Critical Rule: Order your conditions from most specific to least specific. If you put a general condition first, it will catch cases meant for more specific conditions below it!
Grade Classification Example
Breaking down an if-elif-else chain step by step
# if-elif-else chain for grade classification
score = 78
We create a variable called score and assign it the value 78. This is the test score we want to convert into a letter grade.
if score >= 90:
grade = "A"
message = "Excellent work!"
The if statement checks if score is greater than or equal to 90. Since 78 >= 90 is False, this block is skipped and Python moves to the next elif.
elif score >= 80:
grade = "B"
message = "Good job!"
The first elif checks if score is greater than or equal to 80. Since 78 >= 80 is False, this block is also skipped and Python continues to the next condition.
elif score >= 70:
grade = "C"
message = "Satisfactory"
This elif checks if score is greater than or equal to 70. Since 78 >= 70 is True, this block executes! The variables grade and message are assigned. After this, Python exits the entire chain and skips all remaining conditions.
elif score >= 60:
grade = "D"
message = "Needs improvement"
else:
grade = "F"
message = "Please see instructor"
These blocks are never checked because Python already found a matching condition above (score >= 70). Even though 78 >= 60 is also True, this elif is never evaluated. The else block serves as a catch-all for any score below 60.
print(f"Score: {score}")
print(f"Grade: {grade}")
print(f"Feedback: {message}")
These print() statements display the final results using f-strings. The variables contain: score = 78, grade = "C", and message = "Satisfactory".
Console Output
What you see when you run this codeScore: 78
Grade: C
Feedback: Satisfactory
In this example, even though score >= 70 and score >= 60 are both true (78 satisfies both), only the first matching condition executes. Once Python finds a true condition, it skips all remaining elif and else blocks.
Common Mistake: Wrong Order
The order of your conditions matters enormously! Here is a comparison:
score = 95
if score >= 60: # True for 95!
grade = "D" # WRONG!
elif score >= 70:
grade = "C"
elif score >= 80:
grade = "B"
elif score >= 90:
grade = "A" # Never reached!
# Result: grade = "D" (Wrong!)
score = 95
if score >= 90: # Most specific first
grade = "A" # Catches 90+
elif score >= 80:
grade = "B" # Catches 80-89
elif score >= 70:
grade = "C" # Catches 70-79
elif score >= 60:
grade = "D" # Catches 60-69
# Result: grade = "A" (Correct!)
Multiple Independent Conditions
Sometimes you need to check conditions that are independent of each other - where multiple conditions could all be true and you want to act on each one. In this case, use separate if statements instead of elif.
elif when conditions are mutually exclusive (only one should run). Use multiple ifs when conditions are independent (several might run).# Multiple independent conditions (not elif!)
user_age = 25
user_income = 60000
has_good_credit = True
We set up three variables to represent a loan applicant's profile. Each variable stores different information: age (25 years), annual income ($60,000), and credit status (good credit). These will be checked independently.
print("Loan eligibility check:")
This prints a header message to introduce what the program is doing. It helps make the output clear and organized for the user.
# Each condition is checked independently
if user_age >= 18:
print("- Age requirement: PASSED")
The first if statement checks if the user is at least 18 years old. Since user_age is 25, this condition is True and the message prints. Notice this is a standalone if, not connected to the others.
if user_income >= 50000:
print("- Income requirement: PASSED")
The second if statement checks if income is at least $50,000. This is a completely separate check from the age check. Both can pass, both can fail, or any combination. Since income is $60,000, this also passes.
if has_good_credit:
print("- Credit requirement: PASSED")
The third if checks the credit status. Since has_good_credit is already a boolean (True), we don't need to compare it to anything. Python evaluates it directly. This also passes.
# All three conditions are checked separately
# Output:
# Loan eligibility check:
# - Age requirement: PASSED
# - Income requirement: PASSED
# - Credit requirement: PASSED
The key point: all three messages printed because each if statement runs independently. If we had used elif instead, only the first passing condition would have printed. Use multiple ifs when you want to check and act on multiple conditions separately.
Unlike an elif chain where only one block executes, here each if statement is evaluated independently. This is useful when you need to check multiple unrelated conditions or perform multiple actions.
Quick Reference: Conditional Structures
| Structure | Use Case | Blocks Executed |
|---|---|---|
if only |
Optional action when condition is true | 0 or 1 |
if-else |
Two mutually exclusive options | Exactly 1 |
if-elif-else |
Multiple mutually exclusive options | Exactly 1 |
Multiple ifs |
Independent conditions | 0 to all |
Practice: if-elif-else
Task: Write a program that takes an age and prints whether the person can vote (18 or older), will be able to vote soon (16-17 years old), or is too young (under 16). This exercise reinforces the if-elif-else chain pattern with age-based ranges. Consider what message would be most helpful for each category.
Show Solution
age = 17
if age >= 18:
print("You are eligible to vote!")
elif age >= 16:
print(f"You will be able to vote in {18 - age} year(s)")
else:
print("You are too young to vote")
print(f"You need to wait {18 - age} more years")
# Output for age = 17:
# You will be able to vote in 1 year(s)
Task: Create a ticket pricing system where: Children (0-12) pay Rs. 100, Teens (13-17) pay Rs. 200, Adults (18-59) pay Rs. 300, Seniors (60+) pay Rs. 150. This exercise demonstrates that elif chains can check overlapping ranges by testing from most specific to least specific. Order your conditions carefully!
Show Solution
age = 45
if age < 0:
print("Invalid age!")
elif age <= 12:
price = 100
category = "Child"
elif age <= 17:
price = 200
category = "Teen"
elif age <= 59:
price = 300
category = "Adult"
else:
price = 150
category = "Senior"
if age >= 0:
print(f"Category: {category}")
print(f"Ticket Price: Rs. {price}")
# Output for age = 45:
# Category: Adult
# Ticket Price: Rs. 300
Task: Calculate BMI (weight in kg / height in m squared) and classify: Underweight (below 18.5), Normal (18.5-24.9), Overweight (25-29.9), Obese (30+). Include personalized health advice for each category.
Show Solution
weight = 70 # kg
height = 1.75 # meters
bmi = weight / (height ** 2)
print(f"Your BMI: {bmi:.1f}")
if bmi < 18.5:
category = "Underweight"
advice = "Consider consulting a nutritionist for a healthy weight gain plan."
elif bmi < 25:
category = "Normal weight"
advice = "Great job! Maintain your healthy lifestyle."
elif bmi < 30:
category = "Overweight"
advice = "Consider increasing physical activity and reviewing your diet."
else:
category = "Obese"
advice = "Please consult a healthcare provider for personalized guidance."
print(f"Category: {category}")
print(f"Advice: {advice}")
# Output for weight=70, height=1.75:
# Your BMI: 22.9
# Category: Normal weight
# Advice: Great job! Maintain your healthy lifestyle.
Comparison and Logical Operators
The Tools for Building Conditions
Conditions in Python are built using two types of operators: comparison operators (to compare values) and logical operators (to combine multiple conditions). These operators always produce a True or False result.
Comparison Operators
Compare two values and ask questions like:
- "Is this bigger?"
- "Are these equal?"
- "Is this less than that?"
Logical Operators
Combine multiple conditions and ask:
- "Is this true AND that true?"
- "Is this true OR that true?"
- "Is this NOT true?"
Comparison Operators
Comparison operators are symbols used to compare two values. They ask simple yes-or-no questions about your data, and Python answers with True or False.
==
Equal To
Checks if two values are exactly the same. Works with numbers, strings, lists, and more.
5 == 5 → True"hi" == "hi" → True
!=
Not Equal To
Checks if two values are different. Returns True when values don't match.
5 != 3 → True"a" != "b" → True
>
Greater Than
Checks if the left value is strictly larger than the right value.
10 > 5 → True5 > 5 → False
<
Less Than
Checks if the left value is strictly smaller than the right value.
3 < 10 → True5 < 5 → False
>=
Greater Than or Equal
Checks if left is larger OR equal. Useful for "at least" conditions.
5 >= 5 → Trueage >= 18 (voting)
<=
Less Than or Equal
Checks if left is smaller OR equal. Useful for "at most" conditions.
5 <= 5 → Trueitems <= 10 (limit)
==— Checking passwords, comparing user input, validating data!=— Checking if something changed, excluding values>/<— Comparing scores, prices, quantities>=/<=— Age restrictions, minimum/maximum limits, thresholds
Super Important for Beginners!
The difference between = (single equals) and == (double equals) trips up almost everyone at first!
= (single) = Assign a value
== (double) = Compare values
# Assignment vs Comparison - know the difference!
x = 5 # Assignment: x now holds the value 5
x == 5 # Comparison: checks if x equals 5, returns True
x == 10 # Comparison: checks if x equals 10, returns False
# Common mistake in conditionals:
# if x = 5: # WRONG! This is assignment, causes SyntaxError
if x == 5: # CORRECT! This is comparison
print("x is five")
The single equals = stores a value in a variable, while double equals == compares two values and returns True or False.
| Operator | Name | Example | Result |
|---|---|---|---|
== | Equal to | 5 == 5 | True |
!= | Not equal to | 5 != 3 | True |
> | Greater than | 5 > 3 | True |
< | Less than | 5 < 3 | False |
>= | Greater than or equal | 5 >= 5 | True |
<= | Less than or equal | 5 <= 3 | False |
# Comparison operators in action
x = 10
y = 5
print(f"x = {x}, y = {y}")
print(f"x == y: {x == y}") # False (10 is not equal to 5)
print(f"x != y: {x != y}") # True (10 is not equal to 5)
print(f"x > y: {x > y}") # True (10 is greater than 5)
print(f"x < y: {x < y}") # False (10 is not less than 5)
print(f"x >= y: {x >= y}") # True (10 is greater than or equal to 5)
print(f"x <= y: {x <= y}") # False (10 is not less than or equal to 5)
Each comparison returns a boolean value (True or False) that can be used directly in conditionals or stored in variables.
Comparing Strings
How String Comparison Works in Python
Strings are compared character by character based on their Unicode values (like alphabetical order, but for all characters). Python compares the first characters, then the second, and so on until it finds a difference.
Case Sensitivity: "Apple" and "apple" are NOT equal! Uppercase letters have different Unicode values than lowercase letters.
Equality
"hello" == "hello" → True
"Hello" == "hello" → False
Must match exactly, including case
Alphabetical Order
"apple" < "banana" → True
"zebra" > "apple" → True
"a" comes before "b" in alphabet
Case Order
"A" < "a" → True
"Z" < "a" → True
All uppercase before lowercase
# String comparison
print("apple" == "apple") # True (exact match)
print("apple" == "Apple") # False (case-sensitive!)
print("apple" < "banana") # True (a comes before b)
print("Apple" < "apple") # True (uppercase A has lower Unicode value)
# Common use case: case-insensitive comparison
name1 = "Alice"
name2 = "alice"
print(name1.lower() == name2.lower()) # True (convert both to lowercase first)
To compare strings without worrying about case, use .lower() or .upper() to convert both strings to the same case before comparing.
.lower()or.upper()— Use for case-insensitive comparison.strip()— Remove whitespace before comparing user inputinoperator — Check if substring exists (e.g.,"or" in "World").startswith()/.endswith()— Check beginning or end of strings
Logical Operators
Logical operators combine multiple conditions into one. Python has three logical operators:
and
Both Must Pass
Returns True only if BOTH conditions are true. If either one is false, the whole expression is false.
True and True → TrueTrue and False → FalseFalse and True → FalseFalse and False → FalseExample: age >= 18 and has_id — Must be adult AND have ID
or
Either Can Pass
Returns True if AT LEAST ONE condition is true. Only false if both are false.
True or True → TrueTrue or False → TrueFalse or True → TrueFalse or False → FalseExample: is_admin or is_owner — Either role has access
not
Flip the Answer
Inverts a boolean value. Turns True to False and vice versa. Only takes ONE operand.
not True → Falsenot False → Truenot (5 > 3) → Falsenot (5 < 3) → TrueExample: not is_banned — Allow if NOT banned
Real-World Examples of Logical Operators
Login System (and)
username_correct and password_correct
Both must match to allow login
Discounts (or)
is_member or has_coupon
Either condition gives discount
Moderation (not)
not is_spam
Show comment if NOT spam
| Operator | Description | Example | Result |
|---|---|---|---|
and | Both must be True | True and False | False |
or | At least one must be True | True or False | True |
not | Inverts the boolean | not True | False |
# Logical operators
age = 25
has_license = True
has_insurance = False
We create three variables to represent a person's profile. age stores a number (25), while has_license and has_insurance store boolean values (True/False). These will be used to demonstrate how logical operators work.
# and: both conditions must be True
can_rent_car = age >= 21 and has_license
print(f"Can rent car: {can_rent_car}") # True (25 >= 21 AND has license)
The and operator requires BOTH conditions to be True. Here we check if age is at least 21 (True, because 25 >= 21) AND if the person has a license (True). Since both are True, can_rent_car becomes True.
# or: at least one condition must be True
needs_attention = age < 18 or age > 65
print(f"Needs attention: {needs_attention}") # False (neither condition is True)
The or operator requires AT LEAST ONE condition to be True. We check if age is under 18 (False) OR over 65 (False). Since both are False, needs_attention is False. If either condition were True, the result would be True.
# not: inverts the boolean value
is_uninsured = not has_insurance
print(f"Is uninsured: {is_uninsured}") # True (not False = True)
The not operator flips a boolean value. Since has_insurance is False, not has_insurance becomes True. This is useful when you want to check the opposite of a condition.
# Combining multiple operators
is_eligible = (age >= 21) and has_license and (not has_insurance)
print(f"Eligible for special offer: {is_eligible}") # True
You can combine multiple logical operators in one expression. This checks three things: age >= 21 (True), has_license (True), and not has_insurance (True). Since all three are True, the result is True. Parentheses make the expression easier to read.
Short-Circuit Evaluation
Python is smart! It stops evaluating as soon as it knows the final result:
and — If first is False, skip the rest (result is already False)
or — If first is True, skip the rest (result is already True)
# Safe division using short-circuit evaluation
x = 10
y = 0
# The second condition only runs if y != 0
if y != 0 and x / y > 5:
print("Result is greater than 5")
# Without short-circuit, x / y would cause ZeroDivisionError!
Short-circuit evaluation is useful for safety checks — the division only happens if y is not zero.
Chained Comparisons
What are Chained Comparisons?
Python allows you to chain comparison operators together, just like in mathematics! Instead of writing x > 0 and x < 10, you can write 0 < x < 10. This is more readable and closer to how we think naturally.
Bonus: Chained comparisons are also more efficient — Python evaluates each value only once, which matters when values come from expensive function calls.
Traditional Way (Other Languages)
# Verbose and repetitive
if score >= 70 and score < 80:
grade = "C"
# Check if x is between 1 and 10
if x > 1 and x < 10:
print("In range")
Python's Chained Way (Better!)
# Clean and mathematical
if 70 <= score < 80:
grade = "C"
# Check if x is between 1 and 10
if 1 < x < 10:
print("In range")
# Chained comparisons - Python allows this!
score = 75
# Instead of: score >= 70 and score < 80
if 70 <= score < 80:
print("Grade: C")
# Multiple chained comparisons
x = 5
print(1 < x < 10) # True (x is between 1 and 10)
print(1 < x < 3) # False (x is not between 1 and 3)
print(0 < x == 5) # True (x > 0 AND x equals 5)
# Real-world example: time validation
hour = 14
if 9 <= hour < 17:
print("Business hours") # This prints for hour = 14
else:
print("Closed")
Chained comparisons make range checks intuitive. 70 <= score < 80 reads as "score is at least 70 and less than 80."
0 <= percentage <= 100— Validating percentage is within valid range18 <= age < 65— Checking working age population9 <= hour < 17— Business hours check'a' <= char <= 'z'— Checking if character is lowercase letter
Identity and Membership Operators
Identity Operators
Check if two variables point to the exact same object in memory, not just equal values.
is
Same object?
is not
Different object?
Membership Operators
Check if a value exists within a collection like lists, strings, tuples, or dictionaries.
in
Is inside?
not in
Not inside?
== vs is:
Think of identical twins. They look the same (equal values), but they are different people (different objects). == checks if they look the same, is checks if they are literally the same person.
a = [1, 2, 3]b = [1, 2, 3]a == b → True (same values)a is b → False (different objects)
a = [1, 2, 3]c = a (c points to same list)a == c → True (same values)a is c → True (same object!)
# Identity operators (check if same object in memory)
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True (same values)
print(a is b) # False (different objects in memory)
print(a is c) # True (c points to same object as a)
# None should always be checked with 'is'
result = None
if result is None:
print("No result yet")
The is operator is mainly used for checking None. For most comparisons, use ==.
Where Can You Use in?
Lists
"a" in [1, "a", 3]
Strings
"or" in "World"
Dictionaries
"key" in my_dict
Tuples/Sets
5 in (1, 5, 10)
# Membership operators
fruits = ["apple", "banana", "cherry"]
print("apple" in fruits) # True
print("mango" not in fruits) # True
# Works with strings too
message = "Hello, World!"
if "World" in message:
print("Found 'World' in the message")
# Check dictionary keys
user = {"name": "Alice", "age": 25}
if "email" not in user:
print("Email not provided") # This prints
# Useful for input validation
valid_choices = ["yes", "no", "maybe"]
user_input = "yes"
if user_input.lower() in valid_choices:
print(f"Valid choice: {user_input}")
The in operator is perfect for checking if user input is valid, if a key exists in a dictionary, or if a substring is in a larger string.
is only for comparing with None, True, or False. For comparing values, always use ==. Using is for value comparison can lead to unexpected bugs!
| Operator | Type | Description | Example | Result |
|---|---|---|---|---|
is | Identity | Same object in memory | x is None | Depends |
is not | Identity | Different objects | x is not None | Depends |
in | Membership | Value exists in collection | "a" in ["a", "b"] | True |
not in | Membership | Value not in collection | "c" not in ["a", "b"] | True |
Practice: Operators
Task: Write code to check if a percentage score is valid (between 0 and 100 inclusive) using chained comparison. Python's chained comparisons like 0 <= score <= 100 make range validation elegant and readable. This is much cleaner than score >= 0 and score <= 100.
Show Solution
score = 85
if 0 <= score <= 100:
print(f"Valid score: {score}%")
else:
print(f"Invalid score: {score}. Must be between 0-100")
# Output: Valid score: 85%
# Test with invalid score
score = 150
if 0 <= score <= 100:
print(f"Valid score: {score}%")
else:
print(f"Invalid score: {score}. Must be between 0-100")
# Output: Invalid score: 150. Must be between 0-100
Task: Create a login system that checks: username must be at least 4 characters, password must be at least 8 characters, and they cannot be the same (case-insensitive). Use logical operators to combine checks. Provide specific error messages for each failed validation so users know exactly what to fix.
Show Solution
username = "alice"
password = "securePass123"
# Individual checks
valid_username = len(username) >= 4
valid_password = len(password) >= 8
not_same = username.lower() != password.lower()
# Combined validation
if valid_username and valid_password and not_same:
print("Login credentials are valid!")
else:
print("Invalid credentials:")
if not valid_username:
print("- Username must be at least 4 characters")
if not valid_password:
print("- Password must be at least 8 characters")
if not not_same:
print("- Username and password cannot be the same")
# Output: Login credentials are valid!
Task: Create an access control system where: admins can access everything (settings, users, reports, logs, database), managers can access reports and users only, regular users can only view their own data. This models real-world role-based access control (RBAC) and demonstrates combining membership operators with logical operators.
Show Solution
user_role = "manager"
requested_resource = "reports"
user_id = "user123"
resource_owner = "user456"
# Define access permissions
admin_resources = ["settings", "users", "reports", "logs", "database"]
manager_resources = ["users", "reports"]
user_resources = ["own_profile", "own_data"]
def check_access(role, resource, user_id, owner_id):
# Admins can access everything
if role == "admin":
return True
# Managers can access specific resources
if role == "manager" and resource in manager_resources:
return True
# Users can only access their own data
if role == "user":
is_own_resource = resource in user_resources
is_owner = user_id == owner_id
if is_own_resource or is_owner:
return True
return False
# Test access
has_access = check_access(user_role, requested_resource, user_id, resource_owner)
print(f"Role: {user_role}")
print(f"Requesting: {requested_resource}")
print(f"Access granted: {has_access}")
# Output:
# Role: manager
# Requesting: reports
# Access granted: True
Nested Conditionals
What are Nested Conditionals?
Sometimes you need to make a decision that depends on a previous decision. Nested conditionals are simply an if statement inside another if statement! Each level of nesting represents a deeper decision that only makes sense if the previous condition was True.
Think of it like: A choose-your-own-adventure book. "If you have a sword, go to page 10. On page 10, if the dragon is sleeping, sneak past. If the dragon is awake, prepare to fight." The second choice only makes sense if you already have the sword!
Decision Tree
Each level of nesting takes you deeper into your decision tree. Inner conditions only run if outer conditions pass.
Pyramid of Doom
Too much nesting (4+ levels) creates hard-to-read code. Look for ways to "flatten" when possible.
When to Use
Use when decisions are truly dependent - the inner check only makes sense if the outer check passed first.
Basic Nested Structure
ATM Withdrawal System
Imagine you are programming an ATM machine. You cannot just give people money right away - you need to check several things in order. Each check only makes sense if the previous one passed.
Card Inserted?
First, check if they inserted a card
PIN Correct?
Only THEN check the PIN
Enough Balance?
Only THEN check the balance
Dispense Cash!
Only THEN give the cash
# Nested conditional example - ATM withdrawal
has_card = True
correct_pin = True
balance = 5000
withdrawal_amount = 3000
We set up four variables to simulate an ATM transaction: a card is inserted, the PIN is correct, the account has Rs. 5000, and the user wants to withdraw Rs. 3000.
if has_card:
print("Card detected")
The outermost if checks if a card is inserted. Only when this is True do we proceed to check anything else. This is the first "gate" in our security chain.
if correct_pin:
print("PIN verified")
Inside the first if, we check the PIN. This is a nested if - it only runs if the card check passed. Notice the extra indentation showing it's inside the first block.
if withdrawal_amount <= balance:
balance -= withdrawal_amount
print(f"Dispensing Rs. {withdrawal_amount}")
print(f"New balance: Rs. {balance}")
The deepest nested if checks if there's enough money. If all three conditions pass, we subtract the amount from the balance and dispense the cash. This is the "happy path" - everything went right!
else:
print("Insufficient funds!")
else:
print("Incorrect PIN!")
else:
print("Please insert card")
Each else handles the failure case at its level. The error messages are specific to which check failed: no card, wrong PIN, or not enough money. The indentation matches the if it belongs to.
- Outer check: Does the user have a card? ✓ Yes, so we continue inside.
- First nested: Is the PIN correct? ✓ Yes, so we continue deeper.
- Second nested: Is 3000 <= 5000? ✓ Yes, so we dispense the cash!
Each level of nesting depends on the previous check passing. It would not make sense to check the balance before verifying the PIN, or to dispense money before checking the balance. This dependency chain is what makes nested conditionals appropriate here.
Flattening Nested Conditions
Early Returns & Guard Clauses
Instead of nesting deeper and deeper, you can "flatten" your code using early returns or guard clauses. Check for all the "bad" cases at the start and exit early if any are true. This leaves the "happy path" (successful case) at the end.
Compare these two approaches - they do the exact same thing, but one is much easier to read:
Deep Nesting (Harder to Read)
# Deep nesting (harder to read)
def process_order_nested(order):
We define a function called process_order_nested that takes an order dictionary as input. This function needs to verify several things before processing the order: the order must exist, have a pending status, contain items, and have verified payment. The nested approach checks these conditions by going deeper and deeper into indentation levels.
if order is not None:
First level of nesting: We check if the order object actually exists (is not None). If someone calls this function without an order, we don't want the code to crash. Notice that ALL the remaining logic will be indented inside this check — if the order is None, we skip everything and fall through to the final return False.
if order["status"] == "pending":
Second level of nesting: Inside the first check, we verify the order status is "pending". Orders that are already "shipped", "cancelled", or "completed" shouldn't be processed again. Notice we're now 2 indentation levels deep (8 spaces). The code is starting to drift rightward — this is the beginning of the "pyramid" shape.
if order["items"]:
Third level of nesting: We check if the order actually contains any items. An empty list [] is "falsy" in Python, so if order["items"]: returns False for empty orders. We're now 3 indentation levels deep (12 spaces). The code is visibly forming a pyramid shape, making it harder to track which if each else would belong to.
if order["payment_verified"]:
print("Processing order...")
return True
return False
Fourth level of nesting: Finally, at 4 levels deep (16 spaces of indentation!), we check if payment was verified. Only after passing ALL four nested checks do we actually process the order and return True. The lonely return False at the bottom catches ANY failure from ANY level — but which check failed? We have no idea without debugging. This "pyramid of doom" is confusing, error-prone, and difficult to maintain.
Flattened Version (Cleaner!)
# Flattened version using early returns (cleaner!)
def process_order_flat(order):
This is the exact same logic, rewritten using the guard clause pattern. The key idea: instead of nesting successful cases, we check for failure cases and exit immediately using return. This eliminates nesting entirely! Each check is independent, and the code stays flat at one indentation level.
if order is None:
return False
Guard clause 1: If the order doesn't exist, we immediately return False and exit the function. The key difference from nesting: we check for the failure condition (is None) instead of the success condition (is not None). Once this check passes, we KNOW the order exists for all remaining code — we can mentally "forget" about this possibility.
if order["status"] != "pending":
return False
Guard clause 2: If the status is NOT pending, exit immediately. Notice the logic is inverted: we use != instead of ==. We're asking "is this condition BAD?" instead of "is this condition good?". If the status is wrong, we bail out. If we get past this line, we KNOW the status is pending — no need to track nested state.
if not order["items"]:
return False
Guard clause 3: If there are no items in the order (empty list), exit. The not keyword inverts the truthiness check. Each guard clause reads like a simple rule: "If X is wrong, stop." There's no hunting through nested braces to understand the logic. By this point, we KNOW: order exists, status is pending, and there are items.
if not order["payment_verified"]:
return False
Guard clause 4: The final check — if payment isn't verified, exit. We've now handled ALL possible failure cases at the same indentation level. Each guard clause is like a bouncer at a club: "No ID? You're out. Wrong dress code? You're out." Only the valid cases make it through the gauntlet to the next line.
print("Processing order...")
return True
The "happy path"! If the code reaches this point, we know for certain: the order exists, status is pending, there are items, and payment is verified. All four guards passed! The main business logic sits at the end, completely unindented from any conditional blocks. This is the beauty of guard clauses — the important code is easy to find, and you don't need to mentally track multiple nested conditions to understand when it runs.
Benefits of Flattening Code
Handle edge cases first, then forget about them
Less indentation makes code easier to scan
Each guard handles one specific failure case
Each guard clause can be tested independently
When to Use Nested vs Flat
Use nested conditionals when the logic truly requires hierarchical decision-making. Use flat conditionals with logical operators when you are just checking multiple requirements for the same outcome.
# Using logical operators instead of nesting
age = 25
has_license = True
has_insurance = True
# Nested (unnecessary complexity)
if age >= 18:
if has_license:
if has_insurance:
print("You can drive")
# Flattened with 'and' (better!)
if age >= 18 and has_license and has_insurance:
print("You can drive")
# With meaningful variable for even better readability
meets_driving_requirements = age >= 18 and has_license and has_insurance
if meets_driving_requirements:
print("You can drive")
Practice: Nested Conditionals
Task: Calculate shipping cost based on: domestic/international, weight (light below 2kg, heavy 2kg or more), and express/standard. Use the pricing structure: Domestic standard light: Rs.50, Domestic standard heavy: Rs.100, Domestic express: double standard prices, International: 3x domestic prices. This demonstrates when nested conditionals naturally model hierarchical decisions.
Show Solution
is_international = False
weight = 1.5 # kg
is_express = True
# Calculate base shipping cost
if is_international:
if weight < 2:
base_cost = 50 * 3 # 150
else:
base_cost = 100 * 3 # 300
else: # domestic
if weight < 2:
base_cost = 50
else:
base_cost = 100
# Apply express multiplier
if is_express:
final_cost = base_cost * 2
else:
final_cost = base_cost
# Output
shipping_type = "International" if is_international else "Domestic"
weight_cat = "Heavy" if weight >= 2 else "Light"
speed = "Express" if is_express else "Standard"
print(f"Shipping: {shipping_type} {speed} ({weight_cat})")
print(f"Cost: Rs. {final_cost}")
# Output:
# Shipping: Domestic Express (Light)
# Cost: Rs. 100
Task: Refactor the deeply nested function below to use early returns and guard clauses. The goal is to flatten the code by handling failure cases first (returning early), leaving the success path at the end without deep indentation. This is a key professional programming skill.
# Original nested code
def validate_user(user):
if user is not None:
if user.get("active"):
if user.get("email"):
if "@" in user["email"]:
if user.get("age", 0) >= 18:
return "User is valid"
return "User is invalid"
Show Solution
def validate_user(user):
"""Validate user with guard clauses - much cleaner!"""
# Guard clause: check if user exists
if user is None:
return "User is invalid: No user provided"
# Guard clause: check if user is active
if not user.get("active"):
return "User is invalid: Account not active"
# Guard clause: check if email exists
email = user.get("email")
if not email:
return "User is invalid: No email provided"
# Guard clause: check email format
if "@" not in email:
return "User is invalid: Invalid email format"
# Guard clause: check age
if user.get("age", 0) < 18:
return "User is invalid: Must be 18 or older"
# All checks passed!
return "User is valid"
# Test the refactored function
test_user = {
"active": True,
"email": "alice@example.com",
"age": 25
}
print(validate_user(test_user)) # User is valid
invalid_user = {"active": True, "email": "no-at-sign"}
print(validate_user(invalid_user)) # User is invalid: Invalid email format
Ternary (Conditional) Operator
What is the Ternary Operator?
Sometimes writing a full if-else block feels like too much work for a simple decision. Python has a shortcut called the ternary operator (also called a "conditional expression") that lets you write a simple if-else in just one line!
Why "Ternary"? The name just means "three parts" — and that's exactly what this operator has: the true value, the condition, and the false value.
Value if True
"adult"
What you get when condition passes
The Condition
if age >= 18
The test that returns True/False
Value if False
else "minor"
What you get when condition fails
The Complete Syntax
value_if_true if condition else value_if_false
Reads like English: "give me 'adult' if age is 18 or more, otherwise give me 'minor'"
Basic Syntax
Let us compare a regular if-else with its ternary equivalent:
# Traditional if-else
age = 20
if age >= 18:
status = "adult"
else:
status = "minor"
# Same thing with ternary operator (one line!)
status = "adult" if age >= 18 else "minor"
print(status) # adult
# Using ternary in print statements
score = 85
print(f"Result: {'Pass' if score >= 40 else 'Fail'}") # Result: Pass
# Assigning different values
temperature = 35
message = "Hot day!" if temperature > 30 else "Pleasant weather"
print(message) # Hot day!
Common Use Cases
The ternary operator shines in situations where you need to quickly choose between two values. It keeps your code concise without sacrificing clarity.
# Use case 1: Setting default values
user_name = ""
display_name = user_name if user_name else "Guest"
print(f"Welcome, {display_name}") # Welcome, Guest
# Use case 2: Pluralization
item_count = 5
message = f"You have {item_count} item{'s' if item_count != 1 else ''}"
print(message) # You have 5 items
# Use case 3: Formatting output
balance = -500
formatted = f"Rs. {balance}" if balance >= 0 else f"Rs. ({abs(balance)})"
print(formatted) # Rs. (500)
# Use case 4: Boolean conversion with meaning
is_active = True
status_text = "Active" if is_active else "Inactive"
print(status_text) # Active
When the Ternary Operator Shines
Default Values
When a variable might be empty or None, provide a fallback value instantly.
Pluralization
Dynamically add "s" to words based on count (1 item vs 2 items).
Formatting
Display negative numbers differently, like wrapping in parentheses for accounting.
Human-Readable Conversion
Convert boolean flags (True/False) to descriptive text.
Chained Ternary (Use Sparingly!)
Warning for Beginners!
You CAN chain multiple ternary operators together, but just because you can does not mean you should! Chained ternaries quickly become unreadable. Look at this example and try to understand what it does:
# Chained ternary (can be hard to read)
score = 75
We set a test score of 75. We want to convert this number into a letter grade (A, B, C, D, or F).
grade = "A" if score >= 90 else "B" if score >= 80 else "C" if score >= 70 else "D" if score >= 60 else "F"
print(grade) # C
This is a chained ternary - multiple ternary operators connected together. It checks: if score >= 90 return "A", else if score >= 80 return "B", else if score >= 70 return "C", and so on. Since 75 >= 70, it returns "C". But notice how hard this is to read!
# The same logic is clearer as if-elif-else
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"
This is the exact same logic written as if-elif-else. It does the same thing but is MUCH easier to read and understand. Each condition is on its own line, and the structure is clear.
# Better alternative: use a function for complex logic
def get_grade(score):
if score >= 90: return "A"
if score >= 80: return "B"
if score >= 70: return "C"
if score >= 60: return "D"
return "F"
grade = get_grade(75) # C
Even better: put the logic in a function! This uses early returns (each return exits the function immediately). The function is reusable and the code is clean. This is what experienced programmers prefer.
Practice: Ternary Operator
Task: Convert this if-else to a single line ternary expression. The modulo operator % returns the remainder of division, so number % 2 == 0 checks if a number is even. This is a perfect use case for the ternary operator because we are simply choosing between two strings.
number = 7
if number % 2 == 0:
parity = "even"
else:
parity = "odd"
Show Solution
number = 7
parity = "even" if number % 2 == 0 else "odd"
print(f"{number} is {parity}") # 7 is odd
Task: Calculate final price with discount: 20% off if total is Rs. 1000 or more, 10% off if total is Rs. 500 or more, no discount otherwise. Use ternary operators to determine the discount percentage. While chained ternaries can be hard to read, this is a borderline acceptable use case since it is still relatively short.
Show Solution
total = 750
# Determine discount percentage using chained ternary
discount_percent = 20 if total >= 1000 else 10 if total >= 500 else 0
# Calculate discount amount and final price
discount_amount = total * (discount_percent / 100)
final_price = total - discount_amount
print(f"Original: Rs. {total}")
print(f"Discount: {discount_percent}% (Rs. {discount_amount})")
print(f"Final Price: Rs. {final_price}")
# Output:
# Original: Rs. 750
# Discount: 10% (Rs. 75.0)
# Final Price: Rs. 675.0
Match-Case (Python 3.10+)
What is Match-Case?
Python 3.10 introduced match-case — a more elegant way to write "if this value is A, do this; if it is B, do that; if it is C, do something else". Instead of checking conditions one by one, you describe patterns that your data might match!
Think of it like: "Hey Python, look at this value and tell me which of these shapes it fits!" It's like a more powerful version of if-elif-else.
Version Requirement
This feature only works in Python 3.10 or newer. Check your version by running python --version in your terminal. If you're using an older version, stick with if-elif-else chains.
Match Exact Values
"Is this exactly 'Monday'?"
Match Multiple
"Is this 'Sat' OR 'Sun'?"
Destructure Data
"Pull apart lists/tuples!"
Add Conditions
"Is x > 100?"
Basic Match-Case Syntax
- Start with
matchfollowed by the value you want to check - Add multiple
caseblocks, each with a pattern to match - Use
case _:as a "catch-all" default (the underscore matches anything)
Here is a simple example that checks what day of the week it is:
# Basic match-case (Python 3.10+)
def get_day_type(day):
We define a function that takes a day name as a string. This function will categorize days into different types like "Weekend", "Work day", etc.
match day.lower():
The match keyword starts the pattern matching. We call .lower() to convert the input to lowercase, so "Saturday", "SATURDAY", and "saturday" all work the same way.
case "saturday" | "sunday":
return "Weekend - Time to relax!"
The pipe symbol | means "OR" — this case matches if the day is "saturday" OR "sunday". Both weekend days share the same response. This is cleaner than writing two separate cases!
case "monday":
return "Start of the work week"
A simple exact match — if the day is exactly "monday", return this specific message. Monday gets its own special (dreaded?) message.
case "friday":
return "TGIF! Almost weekend!"
Another exact match for Friday. TGIF = "Thank God It's Friday!" — Friday also deserves its own celebration message.
case "tuesday" | "wednesday" | "thursday":
return "Regular work day"
Multiple values with | again — Tuesday, Wednesday, and Thursday are all "regular" work days, so they share one response. Much cleaner than three separate if statements!
case _:
return "Invalid day"
The wildcard pattern _ matches ANYTHING not caught by previous cases. This is your "default" or "else" case. If someone passes "xyz" or "banana", this catches it.
print(get_day_type("Saturday")) # Weekend - Time to relax!
print(get_day_type("Monday")) # Start of the work week
print(get_day_type("xyz")) # Invalid day
Testing our function: "Saturday" matches the weekend case (converted to lowercase first), "Monday" matches its exact case, and "xyz" falls through to the wildcard default.
Pattern Matching with Values
Here is where match-case gets really useful! You can add extra conditions (called "guards") using the if keyword. This lets you match ranges of values instead of listing every single one.
For example, instead of writing case 200: case 201: case 202: ... for all successful HTTP codes, you can write case code if 200 <= code < 300: to match ANY number in that range!
# Matching HTTP status codes
def describe_http_status(code):
We define a function that takes an HTTP status code (a number like 200, 404, 500) and returns a human-readable description. This is a perfect use case for match-case!
match code:
We start matching against the code parameter. Python will check each case pattern from top to bottom until it finds one that matches.
case 200:
return "OK - Request successful"
case 201:
return "Created - Resource created"
case 400:
return "Bad Request - Invalid syntax"
case 401:
return "Unauthorized - Authentication required"
case 403:
return "Forbidden - Access denied"
case 404:
return "Not Found - Resource does not exist"
case 500:
return "Internal Server Error"
These are literal matches — exact values we want to handle specifically. The most common HTTP codes get their own descriptive messages. Since these are checked first, they'll match before the range patterns below.
case code if 200 <= code < 300:
return f"Success ({code})"
This is a guard pattern! The case code part captures the value into a variable called code, and the if 200 <= code < 300 part adds an extra condition. This matches ANY success code (200-299) that wasn't caught by the specific cases above.
case code if 400 <= code < 500:
return f"Client Error ({code})"
case code if 500 <= code < 600:
return f"Server Error ({code})"
More guard patterns for client errors (400-499) and server errors (500-599). Instead of listing every possible code, we handle entire ranges with a single case each. The captured code variable lets us include the actual number in the message.
case _:
return f"Unknown status: {code}"
The wildcard pattern _ catches anything not matched above. This acts as our default case for unusual status codes like 600+ or negative numbers.
print(describe_http_status(200)) # OK - Request successful
print(describe_http_status(204)) # Success (204)
print(describe_http_status(418)) # Client Error (418)
Testing our function: 200 matches the specific case, 204 matches the 200-299 range pattern (since there's no specific case for it), and 418 (the famous "I'm a teapot" code!) matches the 400-499 range.
Literal Matching
case 200: matches exact values
Capture + Guard
case x if x > 0: captures AND conditions
Order Matters
Specific cases before general ranges
Wildcard
case _: catches everything else
Matching Sequences and Structures
What is Destructuring?
This is where match-case becomes really powerful! You can match based on the shape of your data and pull out individual pieces at the same time. When you write case (x, y):, Python puts the first number into x and the second into y.
Destructuring = Breaking apart a structure into pieces. It's like opening a gift box and taking out each item separately!
Exact Match
(0, 0)
Is it the origin?
Partial Match
(0, y)
Is first zero? Capture second
Capture Both
(x, y)
Match any, grab both values
With Guard
(x, y) if x == y
Match + extra condition
# Matching tuple structures - coordinate processing
def describe_point(point):
We define a function that takes a point (a tuple like (3, 5)) and returns a description of where it is on a coordinate plane. This is a classic use case for destructuring patterns!
match point:
We start matching against the point tuple. Python will check each case pattern to see if the shape and values match.
case (0, 0):
return "Origin"
Exact match: This only matches the specific tuple (0, 0) — the origin point where both coordinates are zero. No variables are captured; we're matching literal values.
case (0, y):
return f"On Y-axis at y={y}"
Partial match with capture: This matches any tuple where the first element is exactly 0, and captures the second element into variable y. Points like (0, 5) or (0, -3) would match, and we can use y in our response!
case (x, 0):
return f"On X-axis at x={x}"
Another partial match: This matches any tuple where the second element is 0, capturing the first element as x. Points like (3, 0) or (-7, 0) lie on the X-axis.
case (x, y) if x == y:
return f"On diagonal at ({x}, {y})"
Pattern + Guard: This matches any 2-tuple AND captures both values, BUT the guard if x == y adds an extra condition — it only matches if both coordinates are equal. Points like (4, 4) or (7, 7) lie on the diagonal line.
case (x, y):
return f"Point at ({x}, {y})"
General capture: This matches ANY 2-tuple and captures both values. It's the "catch-all" for valid points that didn't match more specific patterns above. This must come AFTER more specific cases!
case _:
return "Not a valid point"
Wildcard: If the input isn't a 2-tuple at all (like a string, a 3-tuple, or None), this catches it and returns an error message.
print(describe_point((0, 0))) # Origin
print(describe_point((0, 5))) # On Y-axis at y=5
print(describe_point((3, 0))) # On X-axis at x=3
print(describe_point((4, 4))) # On diagonal at (4, 4)
print(describe_point((2, 7))) # Point at (2, 7)
Testing all the different cases: origin matches exact (0, 0), (0, 5) matches Y-axis pattern, (3, 0) matches X-axis pattern, (4, 4) matches diagonal guard, and (2, 7) falls through to the general capture.
(0, 0)) before general ones (like (x, y)). Python checks from top to bottom and uses the first match!
Matching with Guards
Guards (using if) add additional conditions to patterns. The pattern only matches if both the structure matches AND the guard condition is true.
Guards are essential when you need to match based on criteria that cannot be expressed with patterns alone. For example, you cannot write a pattern that matches "any number greater than 50" - you need a guard for that. Guards give you the full power of Python expressions to add conditions to your patterns.
The guard condition is evaluated only if the pattern matches. This means you can safely use variables captured by the pattern in the guard condition:
# Using guards for additional conditions
def categorize_score(score):
match score:
case n if n < 0 or n > 100:
return "Invalid score"
case n if n >= 90:
return "A - Excellent"
case n if n >= 80:
return "B - Good"
case n if n >= 70:
return "C - Average"
case n if n >= 60:
return "D - Below Average"
case _:
return "F - Fail"
print(categorize_score(95)) # A - Excellent
print(categorize_score(75)) # C - Average
print(categorize_score(-5)) # Invalid score
python --version.
Practice: Match-Case
Task: Create a function that processes commands like "quit", "help", "save filename", "load filename", and returns appropriate messages. Split the command into parts and use match-case with sequence patterns to destructure commands like ["save", filename] where filename is captured into a variable.
Show Solution
def process_command(command):
parts = command.lower().split()
match parts:
case ["quit"] | ["exit"]:
return "Exiting program..."
case ["help"]:
return "Available commands: quit, help, save , load "
case ["save", filename]:
return f"Saving to {filename}..."
case ["load", filename]:
return f"Loading from {filename}..."
case ["save"] | ["load"]:
return "Error: Please specify a filename"
case []:
return "No command entered"
case _:
return f"Unknown command: {' '.join(parts)}"
# Test the command processor
print(process_command("help")) # Available commands: ...
print(process_command("save data.txt")) # Saving to data.txt...
print(process_command("load")) # Error: Please specify a filename
print(process_command("foo bar")) # Unknown command: foo bar
Task: Create a calculator function that takes a tuple like (num1, operator, num2) and returns the result. Handle +, -, *, /, **, and % operators. Use guards to prevent division by zero. This demonstrates destructuring patterns with guards for comprehensive input validation.
Show Solution
def calculate(expression):
match expression:
case (a, "+", b):
return a + b
case (a, "-", b):
return a - b
case (a, "*", b):
return a * b
case (a, "/", b) if b != 0:
return a / b
case (a, "/", 0):
return "Error: Division by zero"
case (a, "**", b):
return a ** b
case (a, "%", b) if b != 0:
return a % b
case (_, op, _) if op not in ["+", "-", "*", "/", "**", "%"]:
return f"Error: Unknown operator '{op}'"
case _:
return "Error: Invalid expression format"
# Test the calculator
print(calculate((10, "+", 5))) # 15
print(calculate((10, "-", 3))) # 7
print(calculate((4, "*", 7))) # 28
print(calculate((20, "/", 4))) # 5.0
print(calculate((10, "/", 0))) # Error: Division by zero
print(calculate((2, "**", 8))) # 256
print(calculate((10, "&", 5))) # Error: Unknown operator '&'
Key Takeaways
Decision Making
Conditionals let your program make decisions, executing different code based on whether conditions are true or false. Every interactive program relies on this fundamental capability.
if-elif-else Chain
Use if for single checks, if-else for two options, and if-elif-else for multiple mutually exclusive conditions. Remember: order matters and exactly one block executes.
Comparison Operators
Use ==, !=, >, <, >=, <= to compare values. Remember that == checks equality while = is assignment. Python allows chained comparisons like 0 < x < 10.
Logical Operators
Combine conditions with and (both must be true), or (at least one must be true), not (inverts boolean). These operators use short-circuit evaluation for efficiency.
Ternary Operator
Use value_if_true if condition else value_if_false for simple, single-line conditional assignments. Keep it simple - complex ternaries hurt readability.
Match-Case (3.10+)
Structural pattern matching for elegant handling of multiple cases. Supports literal matching, destructuring, guards, and OR patterns. Far more powerful than switch statements.