Why Conditionals Matter
Every non-trivial program needs to make decisions. Should the user be granted access? Is the input valid? Which discount applies? Conditional statements are the fundamental mechanism that lets your program choose between different paths of execution based on whether a condition evaluates to True or False.
Without conditionals, your program would run the same instructions every time, regardless of the data it receives. With conditionals, your code becomes dynamic — it can respond differently to different inputs, handle edge cases, validate data, and implement complex business logic.
# Without conditionals — always prints the same thing
print("Welcome!")
# With conditionals — responds to the situation
user_role = "admin"
if user_role == "admin":
print("Welcome, Administrator! Full access granted.")
else:
print("Welcome! You have standard access.")
Indentation: Python's Block Structure
Unlike languages that use curly braces {} to define code blocks (C, Java, JavaScript), Python uses indentation. This is not optional — it is part of the language syntax. Incorrect indentation causes an IndentationError.
# Correct — 4 spaces of indentation (PEP 8 standard)
if True:
print("This is inside the if block")
print("This is also inside")
print("This is outside — always runs")
# Incorrect — inconsistent indentation
# if True:
# print("Indented with 4 spaces")
# print("Indented with 2 spaces") # IndentationError!
Rules to follow:
- Use 4 spaces per indentation level (PEP 8 standard)
- Never mix tabs and spaces in the same file
- Every line inside a block must be indented at the same level
- An empty block requires the
passplaceholder
# Empty blocks need 'pass'
if True:
pass # Placeholder — does nothing but prevents SyntaxError
The if Statement
The simplest conditional. The indented block runs only if the condition is True.
Syntax:
if condition:
# code to execute when condition is True
Examples:
age = 20
if age >= 18:
print("You are an adult.")
print("You can vote.")
# Output:
# You are an adult.
# You can vote.
temperature = 42
if temperature > 40:
print("Heat warning! Stay hydrated.")
print(f"Current temperature: {temperature}°C")
password = "secret123"
user_input = "secret123"
if user_input == password:
print("Login successful!")
The condition can be any expression that evaluates to a boolean. Python will also implicitly convert non-boolean values using truthiness rules (covered later in this chapter).
The if-else Statement
When you need to handle both outcomes — what happens when the condition is True AND what happens when it is False.
Syntax:
if condition:
# runs when True
else:
# runs when False
Examples:
temperature = 25
if temperature > 30:
print("It's hot outside! Carry water.")
else:
print("The weather is pleasant. Enjoy your day!")
# Output: The weather is pleasant. Enjoy your day!
number = 17
if number % 2 == 0:
print(f"{number} is even")
else:
print(f"{number} is odd")
# Output: 17 is odd
balance = 500
withdrawal = 700
if withdrawal <= balance:
balance -= withdrawal
print(f"Withdrawal successful. Remaining balance: {balance}")
else:
print(f"Insufficient funds. Your balance is {balance}")
# Output: Insufficient funds. Your balance is 500
The if-elif-else Chain
When you have multiple conditions to check, use elif (short for "else if"). Python evaluates each condition top to bottom and executes the first block whose condition is True. Once a match is found, all remaining elif and else blocks are skipped.
Syntax:
if condition_1:
# runs if condition_1 is True
elif condition_2:
# runs if condition_1 is False AND condition_2 is True
elif condition_3:
# runs if condition_1 and condition_2 are False AND condition_3 is True
else:
# runs if ALL conditions above are False
Example: Grading system
score = 78
if score >= 90:
grade = "A+"
elif score >= 80:
grade = "A"
elif score >= 70:
grade = "B"
elif score >= 60:
grade = "C"
elif score >= 50:
grade = "D"
else:
grade = "F"
print(f"Score: {score} -> Grade: {grade}") # Score: 78 -> Grade: B
Key insight: The order of
elifconditions matters. Because Python stops at the firstTruecondition, a score of 78 correctly gets "B" even though it is also>= 60and>= 50. If you reversed the order (checking>= 50first), every score above 50 would get "D".
Example: Traffic light
light = "yellow"
if light == "green":
print("Go!")
elif light == "yellow":
print("Slow down — light is about to turn red.")
elif light == "red":
print("Stop!")
else:
print(f"Unknown signal: {light}")
Example: BMI classifier
weight = 70 # kg
height = 1.75 # metres
bmi = weight / (height ** 2)
if bmi < 18.5:
category = "Underweight"
elif bmi < 25:
category = "Normal weight"
elif bmi < 30:
category = "Overweight"
else:
category = "Obese"
print(f"BMI: {bmi:.1f} — {category}")
# BMI: 22.9 — Normal weight
Note: The
elseblock is optional. If you omit it and none of the conditions match, nothing happens (no error).
Comparison Operators Recap
Conditionals rely on comparison operators. Here is the complete reference:
| Operator | Meaning | Example | Result |
|---|---|---|---|
== | Equal to | 5 == 5 | True |
!= | Not equal to | 5 != 3 | True |
> | Greater than | 7 > 3 | True |
< | Less than | 2 < 8 | True |
>= | Greater than or equal | 5 >= 5 | True |
<= | Less than or equal | 3 <= 1 | False |
Python's chained comparisons (a feature most languages lack):
age = 25
# Instead of: age >= 18 and age <= 65
if 18 <= age <= 65:
print("Working age")
# Works with any number of comparisons
x = 5
print(1 < x < 10 < 100) # True — all comparisons hold
Logical Operators in Conditions
Combine multiple conditions with and, or, and not.
| Operator | Meaning | Example |
|---|---|---|
and | Both must be True | age >= 18 and has_id |
or | At least one must be True | is_student or is_senior |
not | Reverses the boolean | not is_blocked |
age = 25
has_id = True
is_vip = False
# and — both conditions must be true
if age >= 18 and has_id:
print("Entry allowed")
# or — at least one condition must be true
if age < 13 or age > 65:
print("Discounted ticket")
# not — reverses the condition
if not is_vip:
print("Standard seating")
# Combining multiple operators
if age >= 18 and has_id and not is_vip:
print("Regular adult entry")
Precedence of Logical Operators
not has the highest precedence, followed by and, then or:
# This expression:
if a or b and c:
pass
# Is evaluated as:
if a or (b and c):
pass
# NOT as:
# if (a or b) and c:
# When in doubt, use explicit parentheses
if (age >= 18 and has_id) or is_vip:
print("Access granted")
Nested Conditions
You can place if statements inside other if statements. Each nested level adds another layer of indentation.
role = "admin"
is_active = True
has_2fa = False
if role == "admin":
if is_active:
if has_2fa:
print("Full admin access granted")
else:
print("Admin access granted — please enable 2FA")
else:
print("Admin account is deactivated")
else:
print("Standard user access")
When Nesting Is Appropriate
Nested conditions are fine when the logic is genuinely hierarchical — for example, first checking a category, then sub-conditions within that category:
vehicle = "car"
fuel_type = "electric"
year = 2024
if vehicle == "car":
if fuel_type == "electric":
if year >= 2023:
print("Eligible for new EV subsidy")
else:
print("Eligible for standard EV benefits")
elif fuel_type == "hybrid":
print("Hybrid vehicle — partial subsidy")
else:
print("Petrol/diesel — no EV subsidy")
elif vehicle == "bike":
print("Two-wheeler subsidies available")
When to Avoid Deep Nesting
Deeply nested conditions (3+ levels) become hard to read and maintain. Prefer flattening with and/or, guard clauses, or early returns (covered later):
# Hard to read — deeply nested
if user:
if user.is_active:
if user.has_permission("edit"):
if document.is_editable:
edit_document(document)
# Better — flatten with 'and'
if user and user.is_active and user.has_permission("edit") and document.is_editable:
edit_document(document)
# Even better — guard clauses in a function
def try_edit(user, document):
if not user:
return "No user"
if not user.is_active:
return "User inactive"
if not user.has_permission("edit"):
return "No permission"
if not document.is_editable:
return "Document locked"
edit_document(document)
return "Success"
Ternary / Conditional Expressions
Python's one-liner if-else lets you assign values or embed conditions inline. This is called a ternary expression or conditional expression.
Syntax:
value_if_true if condition else value_if_false
Examples:
age = 20
# Standard if-else (4 lines)
if age >= 18:
status = "adult"
else:
status = "minor"
# Ternary expression (1 line)
status = "adult" if age >= 18 else "minor"
print(status) # adult
# In f-strings
print(f"You are {'eligible' if age >= 18 else 'not eligible'} to vote.")
# In function calls
print("Pass" if score >= 50 else "Fail")
# In list comprehensions
numbers = [1, -3, 5, -2, 8]
labels = ["positive" if n > 0 else "non-positive" for n in numbers]
print(labels) # ['positive', 'non-positive', 'positive', 'non-positive', 'positive']
# Assigning different function results
x = 10
result = x * 2 if x > 0 else abs(x)
print(result) # 20
Nested Ternary Expressions
You can chain ternary expressions, but use them sparingly — they become hard to read fast.
score = 75
# Nested ternary — works but not very readable
grade = "A" if score >= 90 else "B" if score >= 80 else "C" if score >= 70 else "F"
print(grade) # C
# How Python parses this (right to left):
grade = "A" if score >= 90 else ("B" if score >= 80 else ("C" if score >= 70 else "F"))
Best practice: Use ternary expressions for simple, one-condition assignments. If you need multiple conditions, use a regular
if-elif-elsechain for readability.
Truthy and Falsy Values
Python does not require conditions to be explicit booleans. Any value can be used in a boolean context, and Python will evaluate it as either "truthy" (treated as True) or "falsy" (treated as False).
Comprehensive Falsy / Truthy Reference
| Value | Truthy or Falsy | Type |
|---|---|---|
False | Falsy | bool |
None | Falsy | NoneType |
0 | Falsy | int |
0.0 | Falsy | float |
0j | Falsy | complex |
"" (empty string) | Falsy | str |
[] (empty list) | Falsy | list |
() (empty tuple) | Falsy | tuple |
{} (empty dict) | Falsy | dict |
set() (empty set) | Falsy | set |
range(0) (empty range) | Falsy | range |
frozenset() | Falsy | frozenset |
b"" (empty bytes) | Falsy | bytes |
True | Truthy | bool |
1, -5, 42 (any non-zero int) | Truthy | int |
3.14, -0.001 (any non-zero float) | Truthy | float |
"hello", " " (any non-empty string) | Truthy | str |
[1, 2], [0] (any non-empty list) | Truthy | list |
{"key": "val"} (any non-empty dict) | Truthy | dict |
| Any object (by default) | Truthy | custom class |
Rule of thumb: Empty and zero-like values are falsy. Everything else is truthy.
Practical Examples of Truthiness
# Checking if a string is non-empty
name = input("Enter your name: ")
if name:
print(f"Hello, {name}!")
else:
print("You didn't enter a name.")
# Same as writing: if name != ""
# Checking if a list has items
cart = ["laptop", "mouse"]
if cart:
print(f"You have {len(cart)} item(s) in your cart.")
else:
print("Your cart is empty.")
# Checking for None (common pattern with function returns)
def find_user(username):
users = {"alice": "Alice Smith", "bob": "Bob Jones"}
return users.get(username) # Returns None if not found
result = find_user("charlie")
if result:
print(f"Found: {result}")
else:
print("User not found")
# Using truthiness to provide defaults
config = {}
db_host = config.get("host") or "localhost"
db_port = config.get("port") or 5432
print(f"Connecting to {db_host}:{db_port}")
# Output: Connecting to localhost:5432
Custom Truthiness with __bool__
You can define truthiness for your own classes by implementing __bool__ or __len__:
class Wallet:
def __init__(self, balance):
self.balance = balance
def __bool__(self):
return self.balance > 0
empty_wallet = Wallet(0)
full_wallet = Wallet(500)
if empty_wallet:
print("Has money")
else:
print("Wallet is empty") # This runs
if full_wallet:
print("Has money") # This runs
Short-Circuit Evaluation in Conditions
Python's and and or operators use short-circuit evaluation — they stop evaluating as soon as the result is determined.
How and Short-Circuits
With and, if the first operand is False, the entire expression is False regardless of the second operand. Python skips evaluating the second part.
x = 0
# Safe! Python never evaluates 10/x because x != 0 is already False
result = x != 0 and 10 / x > 2
print(result) # False (no ZeroDivisionError)
# Without short-circuit, this would crash:
# result = 10 / x > 2 # ZeroDivisionError!
How or Short-Circuits
With or, if the first operand is True, the entire expression is True regardless of the second operand.
is_admin = True
# Python never evaluates the second condition
if is_admin or check_expensive_permission():
print("Access granted")
Short-Circuit for Default Values
A very common Python pattern uses or to provide fallback values:
# 'or' returns the first truthy value
user_name = "" or "Anonymous"
print(user_name) # Anonymous
user_name = "Alice" or "Anonymous"
print(user_name) # Alice
# Chain multiple fallbacks
name = "" or None or 0 or "Default Name"
print(name) # Default Name
Short-Circuit for Safe Attribute Access
# Check if object exists before accessing its attribute
user = None
# Safe — never accesses user.name because 'user' is falsy
if user and user.name:
print(f"Hello, {user.name}")
else:
print("No user logged in")
and Returns the Determining Value
Remember: and and or do not always return True/False. They return the value that determined the outcome:
# 'and' returns the first falsy value, or the last value if all truthy
print(0 and 5) # 0 (first falsy)
print(3 and 5) # 5 (all truthy, returns last)
print("" and "hello") # "" (first falsy)
print("hi" and "hello") # "hello" (all truthy, returns last)
# 'or' returns the first truthy value, or the last value if all falsy
print(0 or 5) # 5 (first truthy)
print(3 or 5) # 3 (first truthy)
print("" or "default") # "default" (first truthy)
print(0 or "" or None) # None (all falsy, returns last)
match-case Statement (Python 3.10+)
Introduced in Python 3.10, match-case provides structural pattern matching — a powerful feature that goes far beyond the traditional switch statement found in other languages. It can match values, destructure data, bind variables, and apply guards.
Basic Value Matching
The simplest use case, similar to switch in other languages:
command = "start"
match command:
case "start":
print("Starting the engine...")
case "stop":
print("Stopping the engine...")
case "pause":
print("Pausing...")
case _:
print(f"Unknown command: {command}")
The _ wildcard matches anything that was not matched by previous cases. It acts as a default/fallback.
Value Binding (Capture Patterns)
match-case can destructure data and bind values to variables:
point = (3, 7)
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"On Y-axis at y={y}")
case (x, 0):
print(f"On X-axis at x={x}")
case (x, y):
print(f"Point at ({x}, {y})")
# Output: Point at (3, 7)
# Matching lists with variable-length patterns
data = [1, 2, 3, 4, 5]
match data:
case []:
print("Empty list")
case [single]:
print(f"One element: {single}")
case [first, second]:
print(f"Two elements: {first}, {second}")
case [first, *rest]:
print(f"First: {first}, Remaining: {rest}")
# Output: First: 1, Remaining: [2, 3, 4, 5]
Guards (Adding Conditions to Cases)
Use if after a pattern to add extra conditions:
age = 15
match age:
case n if n < 0:
print("Invalid age")
case n if n < 13:
print("Child")
case n if n < 18:
print("Teenager")
case n if n < 65:
print("Adult")
case _:
print("Senior")
# Output: Teenager
# Combining destructuring with guards
point = (5, -3)
match point:
case (x, y) if x > 0 and y > 0:
print(f"({x}, {y}) is in Quadrant I")
case (x, y) if x < 0 and y > 0:
print(f"({x}, {y}) is in Quadrant II")
case (x, y) if x < 0 and y < 0:
print(f"({x}, {y}) is in Quadrant III")
case (x, y) if x > 0 and y < 0:
print(f"({x}, {y}) is in Quadrant IV")
case (x, y):
print(f"({x}, {y}) is on an axis")
# Output: (5, -3) is in Quadrant IV
OR Patterns (Multiple Values in One Case)
Use | to match any of several patterns:
status_code = 404
match status_code:
case 200 | 201 | 204:
print("Success")
case 301 | 302:
print("Redirect")
case 400 | 401 | 403:
print("Client error")
case 404:
print("Not found")
case 500 | 502 | 503:
print("Server error")
case _:
print(f"Unknown status: {status_code}")
# Output: Not found
day = "Saturday"
match day.lower():
case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
print("Weekday")
case "saturday" | "sunday":
print("Weekend!")
case _:
print("Invalid day")
Class Patterns
Match against class instances and extract attributes:
class Point:
__match_args__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
class Circle:
__match_args__ = ("center", "radius")
def __init__(self, center, radius):
self.center = center
self.radius = radius
shape = Circle(Point(0, 0), 5)
match shape:
case Circle(center=Point(0, 0), radius=r):
print(f"Circle at origin with radius {r}")
case Circle(center=Point(x, y), radius=r):
print(f"Circle at ({x}, {y}) with radius {r}")
case Point(x, y):
print(f"Just a point at ({x}, {y})")
# Output: Circle at origin with radius 5
Matching Dictionaries
response = {"status": "ok", "data": {"name": "Alice", "age": 30}}
match response:
case {"status": "ok", "data": {"name": name}}:
print(f"Success! User: {name}")
case {"status": "error", "message": msg}:
print(f"Error: {msg}")
case _:
print("Unknown response format")
# Output: Success! User: Alice
Note: Dictionary pattern matching checks for the presence of the specified keys. The dictionary may contain additional keys that are not listed in the pattern.
Common Conditional Patterns
1. Guard Clauses (Early Return)
Instead of wrapping your main logic in layers of if blocks, validate inputs at the top and return early for invalid cases. This keeps the "happy path" logic at the lowest indentation level.
# Without guard clauses — deeply nested
def process_order(order):
if order:
if order.get("quantity", 0) > 0:
if order.get("paid"):
if order.get("in_stock"):
return f"Processing {order['quantity']} items"
else:
return "Item out of stock"
else:
return "Payment required"
else:
return "Invalid quantity"
else:
return "No order provided"
# With guard clauses — flat and readable
def process_order(order):
if not order:
return "No order provided"
if order.get("quantity", 0) <= 0:
return "Invalid quantity"
if not order.get("paid"):
return "Payment required"
if not order.get("in_stock"):
return "Item out of stock"
# Main logic — only reached if all validations pass
return f"Processing {order['quantity']} items"
2. in Checks for Multiple Values
When comparing a variable against several values, use in with a tuple or set instead of chaining or:
# Verbose and repetitive
fruit = "mango"
if fruit == "apple" or fruit == "mango" or fruit == "banana" or fruit == "grape":
print("Valid fruit")
# Clean and Pythonic
if fruit in ("apple", "mango", "banana", "grape"):
print("Valid fruit")
# Use a set for large collections (O(1) lookup)
valid_extensions = {".py", ".js", ".ts", ".java", ".go", ".rs"}
file_ext = ".py"
if file_ext in valid_extensions:
print("Supported file type")
3. Dictionary Dispatch Pattern
Replace long if-elif chains with a dictionary that maps keys to functions or values. This is cleaner and more extensible.
# Long if-elif chain
def get_day_type(day):
if day == "Monday":
return "Start of work week"
elif day == "Tuesday":
return "Second day"
elif day == "Wednesday":
return "Midweek"
elif day == "Thursday":
return "Almost Friday"
elif day == "Friday":
return "TGIF!"
elif day == "Saturday" or day == "Sunday":
return "Weekend!"
else:
return "Invalid day"
# Dictionary dispatch — cleaner
def get_day_type(day):
day_messages = {
"Monday": "Start of work week",
"Tuesday": "Second day",
"Wednesday": "Midweek",
"Thursday": "Almost Friday",
"Friday": "TGIF!",
"Saturday": "Weekend!",
"Sunday": "Weekend!",
}
return day_messages.get(day, "Invalid day")
For dispatching to functions:
def add(a, b): return a + b
def sub(a, b): return a - b
def mul(a, b): return a * b
def div(a, b): return a / b if b != 0 else "Cannot divide by zero"
operations = {
"+": add,
"-": sub,
"*": mul,
"/": div,
}
operator = "+"
result = operations.get(operator, lambda a, b: "Invalid operator")(10, 3)
print(result) # 13
4. Chained Conditionals with all() and any()
When you have many conditions to check, all() and any() can be cleaner than long and/or chains:
# Instead of: if cond1 and cond2 and cond3 and cond4:
conditions = [
user.is_active,
user.age >= 18,
user.has_verified_email,
user.account_balance > 0,
]
if all(conditions):
print("User eligible for premium features")
# Instead of: if cond1 or cond2 or cond3:
warnings = [
disk_usage > 90,
memory_usage > 85,
cpu_temp > 80,
]
if any(warnings):
print("System alert!")
Anti-Patterns to Avoid
1. Deep Nesting (Pyramid of Doom)
# BAD — deeply nested, hard to follow
def validate_user(user):
if user is not None:
if user.name:
if user.email:
if "@" in user.email:
if user.age >= 18:
return True
return False
# GOOD — use guard clauses
def validate_user(user):
if user is None:
return False
if not user.name:
return False
if not user.email or "@" not in user.email:
return False
if user.age < 18:
return False
return True
2. Redundant Boolean Comparisons
# BAD — comparing booleans to True/False
is_active = True
if is_active == True: # Redundant
print("Active")
if is_active == False: # Redundant
print("Inactive")
# GOOD — direct boolean usage
if is_active:
print("Active")
if not is_active:
print("Inactive")
# BAD — returning True/False based on a condition
def is_even(n):
if n % 2 == 0:
return True
else:
return False
# GOOD — the condition IS already a boolean
def is_even(n):
return n % 2 == 0
3. Duplicate Code Across Branches
# BAD — repeated print call
if discount:
price = original_price * 0.8
print(f"Final price: {price}")
else:
price = original_price
print(f"Final price: {price}")
# GOOD — move shared logic outside
price = original_price * 0.8 if discount else original_price
print(f"Final price: {price}")
4. Using if-elif When in Would Suffice
# BAD — verbose
if status == "active" or status == "pending" or status == "verified":
allow_access()
# GOOD — concise
if status in ("active", "pending", "verified"):
allow_access()
5. Ignoring None vs Other Falsy Values
# DANGEROUS — treats 0, "", [] as "no value"
def process(value):
if not value:
print("No value provided")
else:
print(f"Processing: {value}")
process(0) # Prints "No value provided" — but 0 IS a valid value!
process("") # Same problem if empty string is a valid input
# SAFE — explicitly check for None
def process(value):
if value is None:
print("No value provided")
else:
print(f"Processing: {value}")
process(0) # Prints "Processing: 0" — correct!
Practical Examples
Menu-Driven Calculator
A complete interactive calculator that uses conditionals for operation selection and error handling:
def calculator():
print("=" * 40)
print(" Simple Calculator")
print("=" * 40)
print(" 1. Addition (+)")
print(" 2. Subtraction (-)")
print(" 3. Multiplication (*)")
print(" 4. Division (/)")
print(" 5. Modulus (%)")
print(" 6. Power (**)")
print("=" * 40)
choice = input("Select operation (1-6): ").strip()
if choice not in ("1", "2", "3", "4", "5", "6"):
print("Invalid choice! Please select 1-6.")
return
try:
num1 = float(input("Enter first number: "))
num2 = float(input("Enter second number: "))
except ValueError:
print("Invalid input! Please enter numbers only.")
return
if choice == "1":
result = num1 + num2
symbol = "+"
elif choice == "2":
result = num1 - num2
symbol = "-"
elif choice == "3":
result = num1 * num2
symbol = "*"
elif choice == "4":
if num2 == 0:
print("Error: Division by zero!")
return
result = num1 / num2
symbol = "/"
elif choice == "5":
if num2 == 0:
print("Error: Modulus by zero!")
return
result = num1 % num2
symbol = "%"
elif choice == "6":
result = num1 ** num2
symbol = "**"
print(f"\n{num1} {symbol} {num2} = {result}")
calculator()
Ticket Pricing System with Multiple Discounts
A real-world example demonstrating layered conditional logic:
def calculate_ticket_price(age, is_student, is_member, day_of_week, show_time):
"""
Calculate movie ticket price based on multiple factors.
Base price: Rs 300
Discounts are applied cumulatively.
"""
base_price = 300
discount_reasons = []
# Age-based pricing
if age < 5:
print("Free entry for children under 5!")
return 0
elif age < 12:
base_price = 150 # Children's price
discount_reasons.append("Child rate (50% off)")
elif age >= 65:
base_price = 180 # Senior citizen price
discount_reasons.append("Senior citizen rate (40% off)")
# Student discount (10%)
if is_student and 12 <= age < 30:
base_price *= 0.90
discount_reasons.append("Student discount (10%)")
# Membership discount (15%)
if is_member:
base_price *= 0.85
discount_reasons.append("Member discount (15%)")
# Day-based discount
if day_of_week in ("Tuesday", "Wednesday"):
base_price *= 0.80
discount_reasons.append(f"{day_of_week} special (20% off)")
# Matinee discount (shows before 4 PM)
if show_time < 16:
base_price *= 0.90
discount_reasons.append("Matinee discount (10%)")
# Minimum price floor
final_price = max(base_price, 80)
print(f"\n--- Ticket Receipt ---")
if discount_reasons:
print("Discounts applied:")
for reason in discount_reasons:
print(f" - {reason}")
else:
print("No discounts applied (standard rate)")
print(f"Final Price: Rs {final_price:.2f}")
return final_price
# Example usage
calculate_ticket_price(
age=22,
is_student=True,
is_member=True,
day_of_week="Tuesday",
show_time=14
)
Indian Income Tax Calculator (FY 2024-25, New Regime)
A practical multi-slab calculation that demonstrates if-elif-else with arithmetic:
def calculate_income_tax(annual_income):
"""
Calculate income tax under India's New Tax Regime (FY 2024-25).
Slabs:
0 - 3,00,000 : Nil
3,00,001 - 7,00,000 : 5%
7,00,001 - 10,00,000 : 10%
10,00,001 - 12,00,000: 15%
12,00,001 - 15,00,000: 20%
Above 15,00,000 : 30%
Standard deduction: Rs 75,000
"""
if annual_income < 0:
print("Income cannot be negative.")
return
# Apply standard deduction
standard_deduction = 75_000
taxable_income = max(annual_income - standard_deduction, 0)
print(f"Gross Annual Income : Rs {annual_income:>12,.2f}")
print(f"Standard Deduction : Rs {standard_deduction:>12,.2f}")
print(f"Taxable Income : Rs {taxable_income:>12,.2f}")
print("-" * 45)
tax = 0
if taxable_income <= 3_00_000:
tax = 0
elif taxable_income <= 7_00_000:
tax = (taxable_income - 3_00_000) * 0.05
elif taxable_income <= 10_00_000:
tax = (4_00_000 * 0.05) + (taxable_income - 7_00_000) * 0.10
elif taxable_income <= 12_00_000:
tax = (4_00_000 * 0.05) + (3_00_000 * 0.10) + (taxable_income - 10_00_000) * 0.15
elif taxable_income <= 15_00_000:
tax = (4_00_000 * 0.05) + (3_00_000 * 0.10) + (2_00_000 * 0.15) + \
(taxable_income - 12_00_000) * 0.20
else:
tax = (4_00_000 * 0.05) + (3_00_000 * 0.10) + (2_00_000 * 0.15) + \
(3_00_000 * 0.20) + (taxable_income - 15_00_000) * 0.30
# Rebate under Section 87A (income up to 7 lakh)
rebate = 0
if taxable_income <= 7_00_000:
rebate = min(tax, 25_000)
tax -= rebate
# Health and Education Cess (4%)
cess = tax * 0.04
total_tax = tax + cess
print(f"Income Tax : Rs {tax:>12,.2f}")
if rebate > 0:
print(f"Section 87A Rebate : Rs {rebate:>12,.2f}")
print(f"Health & Edu Cess : Rs {cess:>12,.2f}")
print(f"{'=' * 45}")
print(f"Total Tax Liability : Rs {total_tax:>12,.2f}")
print(f"Monthly Tax : Rs {total_tax / 12:>12,.2f}")
effective_rate = (total_tax / annual_income * 100) if annual_income > 0 else 0
print(f"Effective Tax Rate : {effective_rate:.2f}%")
return total_tax
# Examples
print("CASE 1: Rs 6,00,000 income")
calculate_income_tax(6_00_000)
print("\n\nCASE 2: Rs 15,00,000 income")
calculate_income_tax(15_00_000)
print("\n\nCASE 3: Rs 25,00,000 income")
calculate_income_tax(25_00_000)
Login Validation System
Demonstrates combining conditionals for multi-factor validation:
import re
# Simulated user database
users_db = {
"alice": {"password": "Alice@2024", "is_active": True, "failed_attempts": 0},
"bob": {"password": "Bob#Secure1", "is_active": True, "failed_attempts": 4},
"charlie": {"password": "Charlie!99", "is_active": False, "failed_attempts": 0},
}
MAX_FAILED_ATTEMPTS = 5
def validate_login(username, password):
"""Validate user login with multiple checks."""
# Guard clause: empty inputs
if not username or not password:
return "Error: Username and password are required."
# Guard clause: username length
if len(username) < 3:
return "Error: Username must be at least 3 characters."
# Check if user exists
username_lower = username.lower()
if username_lower not in users_db:
return "Error: User not found."
user = users_db[username_lower]
# Check if account is active
if not user["is_active"]:
return "Error: Account is deactivated. Contact support."
# Check if account is locked
if user["failed_attempts"] >= MAX_FAILED_ATTEMPTS:
return "Error: Account locked due to too many failed attempts."
# Verify password
if password != user["password"]:
user["failed_attempts"] += 1
remaining = MAX_FAILED_ATTEMPTS - user["failed_attempts"]
if remaining <= 0:
return "Error: Account is now locked. Contact support."
elif remaining <= 2:
return f"Error: Wrong password. WARNING: {remaining} attempt(s) remaining!"
else:
return f"Error: Wrong password. {remaining} attempt(s) remaining."
# All checks passed — successful login
user["failed_attempts"] = 0 # Reset counter on successful login
return f"Welcome back, {username}! Login successful."
# Test cases
print(validate_login("", "pass")) # Empty username
print(validate_login("alice", "Alice@2024")) # Correct login
print(validate_login("alice", "wrong")) # Wrong password
print(validate_login("bob", "Bob#Secure1")) # Locked account
print(validate_login("charlie", "Charlie!99"))# Deactivated account
print(validate_login("david", "pass123")) # User not found
Practice Exercises
Exercise 1: Positive, Negative, or Zero
Write a program that takes a number as input and prints whether it is positive, negative, or zero. Also print whether it is even or odd (zero is even).
# Expected output for input 7:
# 7 is positive
# 7 is odd
# Expected output for input -4:
# -4 is negative
# -4 is even
# Expected output for input 0:
# 0 is zero
# 0 is even
Exercise 2: Electricity Bill Calculator
Write a function that calculates an electricity bill based on these slabs:
- First 100 units: Rs 5 per unit
- Next 100 units (101-200): Rs 7 per unit
- Next 100 units (201-300): Rs 10 per unit
- Above 300 units: Rs 15 per unit
- Add a 5% surcharge on the total
Test it with 50, 150, 250, and 450 units.
Exercise 3: Triangle Classifier
Write a function that takes three sides of a triangle and determines:
- Whether the sides can form a valid triangle (sum of any two sides must be greater than the third)
- If valid, classify it as equilateral, isosceles, or scalene
- Also check if it is a right triangle (using the Pythagorean theorem)
# Expected:
# classify_triangle(3, 3, 3) -> "Equilateral triangle"
# classify_triangle(3, 4, 5) -> "Scalene right triangle"
# classify_triangle(5, 5, 3) -> "Isosceles triangle"
# classify_triangle(1, 2, 10) -> "Not a valid triangle"
Exercise 4: Rock-Paper-Scissors
Build a rock-paper-scissors game using match-case. The program should:
- Accept user input (rock/paper/scissors)
- Generate a random computer choice
- Determine the winner using pattern matching
- Handle invalid inputs
Exercise 5: ATM Machine Simulator
Write an ATM simulator with the following features:
- Start with a balance of Rs 10,000
- Support operations: check balance, deposit, withdraw, change PIN
- Validate the PIN before any operation (default PIN: 1234)
- Enforce minimum balance of Rs 500
- Lock the account after 3 wrong PIN attempts
- Use guard clauses for all validations
Exercise 6: Student Report Card Generator
Write a program that takes marks for 5 subjects and:
- Validates each mark is between 0 and 100
- Calculates total, percentage, and grade using this table:
- 90%+ : A+ (Distinction)
- 80-89%: A (First Class)
- 70-79%: B (Second Class)
- 60-69%: C (Pass)
- Below 60%: F (Fail)
- Determines pass/fail (must score 40+ in every subject AND have 60%+ overall)
- Prints a formatted report card
Summary
In this chapter, you learned:
if,elif,elsefor branching logic and the importance of condition ordering- Indentation as Python's block structure mechanism (4 spaces per level, never mix tabs and spaces)
- Comparison operators (
==,!=,>,<,>=,<=) and Python's chained comparisons - Logical operators (
and,or,not) with correct precedence rules - Nested conditions and when they are (and are not) appropriate
- Ternary expressions (
value if condition else other) for concise inline conditionals - Truthy and falsy values — understanding which values Python treats as
TrueandFalse - Short-circuit evaluation — how
and/orstop early and return determining values match-case(Python 3.10+) — value matching, destructuring, guards, OR patterns, class patterns- Clean patterns — guard clauses,
inchecks, dictionary dispatch,all()/any() - Anti-patterns — avoiding deep nesting, redundant boolean comparisons, and
Nonevs falsy confusion - Practical programs: calculator, ticket pricing, Indian tax calculator, login validation
Next up: Loops — repeating actions with for and while, loop control with break, continue, and else.