> "Every business policy is, at its core, a conditional statement. Python just lets you write it in a language a computer can enforce."
In This Chapter
- What You Will Learn
- 4.1 Why Decision Logic Is the Engine of Business Software
- 4.2 The if Statement: Your First Decision
- 4.3 if/else: Handling Both Outcomes
- 4.4 if/elif/else: Multiple Branches
- 4.5 Nested Conditionals (and When to Avoid Them)
- 4.6 Truthiness: What Python Considers True and False
- 4.7 match/case: Clean Multi-Branch Logic (Python 3.10+)
- 4.8 Short-Circuit Evaluation in Conditionals
- 4.9 The Ternary Expression (Inline if)
- 4.10 Defensive Patterns: Validating Inputs Before Making Decisions
- 4.11 Decision Examples: Pricing, Credit Checks, Order Routing
- 4.12 A Complete Order Processing Engine
- 4.13 Chapter Summary
- Key Terms
Chapter 4: Control Flow — Making Decisions in Your Programs
"Every business policy is, at its core, a conditional statement. Python just lets you write it in a language a computer can enforce." — Priya Okonkwo, Business Analyst, Acme Corp
What You Will Learn
By the end of this chapter you will be able to:
- Explain why decision logic is the foundation of almost every useful program
- Write
if,elif, andelseblocks to encode business rules - Recognize and avoid the "arrow of doom" — runaway nested conditionals
- Understand Python's truthiness system and use it to write cleaner guards
- Use
match/case(Python 3.10+) for clean multi-branch logic - Apply short-circuit evaluation to write safer, more efficient conditions
- Use ternary (inline
if) expressions where they improve clarity - Validate inputs defensively before acting on them
- Build a complete order-processing decision engine
4.1 Why Decision Logic Is the Engine of Business Software
Every piece of software that does anything useful for a business makes decisions. Not metaphorically — literally. Consider a few examples pulled straight from Acme Corp's daily operations:
- The accounts receivable system decides whether an invoice is overdue before sending a reminder.
- The e-commerce checkout decides whether to apply a promotional code.
- The HR portal decides whether an employee's time-off request can be auto-approved or needs a manager's sign-off.
- Sandra Chen's CRM decides whether to flag a customer as "at risk" based on purchase frequency.
Before you had Python, those decisions were made in one of three ways: by a person doing it manually, by a formula buried in a spreadsheet, or by some proprietary software nobody fully understood. Python gives you a fourth option: write the logic yourself, clearly, in a form that can be read, audited, modified, and shared.
The mechanism Python uses for decisions is control flow. Control flow determines the order in which Python executes your code. Until now, every program you have written has been purely sequential — line 1, then line 2, then line 3. Control flow lets you say: "execute line 3 only if some condition is true." That small capability unlocks an enormous range of programs.
Thinking in Business Rules
Before we touch syntax, here is an exercise that will pay dividends throughout this chapter: think about any business rule you work with and write it in plain English "if-then" form.
For example, Acme Corp's shipping policy might read:
If the order value is greater than $500 and the customer is a Gold-tier account, apply free shipping. If the order value is greater than $250 but the customer is not Gold-tier, apply a $10 shipping credit. Otherwise, standard shipping rates apply.
That sentence contains multiple conditions, multiple outcomes, and a fallback. It is a perfect candidate for Python's if/elif/else structure. The translation from business language to code is, in most cases, nearly word for word. That is not an accident — if, and, or, and else are English words deliberately chosen to mirror how humans state conditional logic.
The skill you are building in this chapter is translation: taking the business rules in your head (or in a policy document) and encoding them as Python that enforces those rules reliably, every time, at scale.
4.2 The if Statement: Your First Decision
The simplest form of a decision in Python is the if statement:
if condition:
# This block runs only when condition is True
do_something()
The condition is any expression Python can evaluate as either True or False. The block — everything indented beneath the if line — runs only when the condition is satisfied. If the condition is False, Python skips the block entirely and continues with the next unindented line.
Let us make this concrete with a real Acme scenario. Priya is writing a script to flag overdue invoices:
days_overdue = 45
customer_name = "Riverside Medical Group"
if days_overdue > 30:
print(f"ALERT: {customer_name}'s invoice is {days_overdue} days overdue.")
print("Escalating to collections team.")
print("Invoice check complete.")
Output:
ALERT: Riverside Medical Group's invoice is 45 days overdue.
Escalating to collections team.
Invoice check complete.
If days_overdue were 15, both print statements inside the if block would be skipped, and only "Invoice check complete." would appear.
The Colon and the Indent
Two syntax rules are non-negotiable:
- The
ifline must end with a colon (:). - Every line in the block must be indented by exactly 4 spaces (the Python community standard).
These rules are why Python code looks the way it does. The indentation is not decorative — it is how Python knows where the block begins and ends. A missing colon or inconsistent indentation will cause a SyntaxError.
4.3 if/else: Handling Both Outcomes
Most business rules have two branches: the "yes" path and the "no" path. Python's else clause handles the second path:
order_value = 320.00
gold_tier = False
if order_value > 500 and gold_tier:
shipping_cost = 0.00
shipping_note = "Free shipping (Gold + high value)"
else:
shipping_cost = 15.00
shipping_note = "Standard shipping"
print(f"Shipping: ${shipping_cost:.2f} — {shipping_note}")
Output:
Shipping: $15.00 — Standard shipping
The else block runs if and only if the if condition evaluated to False. Exactly one of the two blocks always runs — never both, never neither.
A Note on and
In the example above, order_value > 500 and gold_tier uses the and operator. For the entire condition to be True, both sub-conditions must be True. Since order_value (320.00) is not greater than 500, the condition fails even though we need not check gold_tier at all. This behavior — Python stopping evaluation early once it knows the result — is called short-circuit evaluation, and we will revisit it in detail shortly.
4.4 if/elif/else: Multiple Branches
Business rules rarely have only two outcomes. Consider Acme's customer discount tiers:
- Gold accounts: 15% discount
- Silver accounts: 10% discount
- Bronze accounts: 5% discount
- Everyone else: no discount
Python handles this with elif (short for "else if"). You can chain as many elif clauses as your business logic requires:
customer_tier = "Silver"
order_subtotal = 1_200.00
if customer_tier == "Gold":
discount_rate = 0.15
tier_label = "Gold"
elif customer_tier == "Silver":
discount_rate = 0.10
tier_label = "Silver"
elif customer_tier == "Bronze":
discount_rate = 0.05
tier_label = "Bronze"
else:
discount_rate = 0.00
tier_label = "Standard (no tier)"
discount_amount = order_subtotal * discount_rate
final_price = order_subtotal - discount_amount
print(f"Tier: {tier_label}")
print(f"Discount: {discount_rate * 100:.0f}% (${discount_amount:.2f})")
print(f"Final price: ${final_price:.2f}")
Output:
Tier: Silver
Discount: 10% ($120.00)
Final price: $1,080.00
The Order Matters
Python evaluates if/elif/elif/.../else from top to bottom and stops at the first condition that is True. This matters when conditions can overlap. Consider an approval workflow:
credit_score = 720
annual_revenue = 85_000
# WRONG order — a score of 720 would match the first branch and never reach
# the more specific second branch, even if revenue is also a factor.
if credit_score >= 700:
decision = "Pre-approved"
elif credit_score >= 700 and annual_revenue >= 100_000:
decision = "Pre-approved — premium tier" # This line can NEVER be reached
# CORRECT order — more specific conditions come first
if credit_score >= 700 and annual_revenue >= 100_000:
decision = "Pre-approved — premium tier"
elif credit_score >= 700:
decision = "Pre-approved — standard tier"
elif credit_score >= 600:
decision = "Manual review required"
else:
decision = "Declined — does not meet minimum criteria"
When you are writing multi-branch logic, start by listing the most specific conditions first and the most general conditions last. The else block, if present, is always the absolute last resort.
4.5 Nested Conditionals (and When to Avoid Them)
You can place an if statement inside another if block. This is called a nested conditional, and it models situations where a second decision depends on the outcome of a first:
order_value = 3_500.00
customer_tier = "Gold"
is_domestic = True
if customer_tier == "Gold":
if is_domestic:
if order_value >= 3_000:
shipping = "Free overnight"
else:
shipping = "Free standard"
else:
shipping = "Free international freight"
else:
shipping = "Standard rates apply"
This works — but notice the shape of the code. Each if inside another if adds another level of indentation. If you keep nesting, your code starts to look like this:
if condition_a:
if condition_b:
if condition_c:
if condition_d:
# The actual logic finally appears here
do_something()
Programmers call this the arrow of doom (or "pyramid of doom") — the code points rightward like an arrowhead. The problems are real and practical:
- Reading it requires tracking four or more simultaneous conditions in your head.
- Adding a new rule requires figuring out which level of nesting it belongs in.
- Testing it is hard because you have to construct deeply specific scenarios to reach each branch.
- A colleague who inherits the code after you will not thank you.
Escaping the Arrow: Three Techniques
Technique 1: Combine conditions with and
If you have nested if statements that are all in the "positive" branch, you can often flatten them:
# Before (nested):
if customer_tier == "Gold":
if is_domestic:
if order_value >= 3_000:
shipping = "Free overnight"
# After (flat):
if customer_tier == "Gold" and is_domestic and order_value >= 3_000:
shipping = "Free overnight"
Technique 2: Early returns (or early assignment) with guard clauses
Instead of wrapping your main logic in a chain of positive conditions, check for the disqualifying conditions first and exit:
def calculate_expedited_shipping(customer_tier, order_value, is_domestic):
"""Return shipping method for a given order."""
# Guards come first — these are the cases we do NOT handle here.
if customer_tier != "Gold":
return "Standard rates apply"
if not is_domestic:
return "Free international freight"
# Now we are certain: tier is Gold AND order is domestic.
# The remaining condition is simple.
if order_value >= 3_000:
return "Free overnight"
else:
return "Free standard"
The guards make the "happy path" (the main business logic) easy to find at the end.
Technique 3: Use dictionaries for simple value mapping
If your nested conditionals are just looking up a value, a dictionary is often cleaner:
# Instead of:
if region == "Northeast":
if customer_tier == "Gold":
rate = 0.02
elif customer_tier == "Silver":
rate = 0.03
# ... and so on
# Consider:
freight_rates = {
("Northeast", "Gold"): 0.02,
("Northeast", "Silver"): 0.03,
("Southeast", "Gold"): 0.025,
# ... all combinations
}
rate = freight_rates.get((region, customer_tier), 0.05) # 0.05 is the default
The rule of thumb: if your code is more than three levels of nesting deep, refactor it. The goal is code that a colleague can read and understand in thirty seconds.
4.6 Truthiness: What Python Considers True and False
When Python evaluates a condition in an if statement, it does not strictly require a value of True or False. It evaluates any value according to a concept called truthiness — some values are treated as if they were True, and others are treated as if they were False.
Falsy Values
The following values are Falsy — Python treats them as False in a condition:
| Value | Type | Example Use Case |
|---|---|---|
False |
bool | Explicit boolean |
0 |
int | Zero quantity, zero amount |
0.0 |
float | Zero balance |
"" |
str | Empty string (no input given) |
[] |
list | Empty list (no items) |
{} |
dict | Empty dictionary (no data) |
() |
tuple | Empty tuple |
None |
NoneType | Missing or unset value |
Everything else is Truthy — any non-zero number, any non-empty string or collection, any object that is not None.
Practical Business Uses of Truthiness
Checking for missing input:
customer_email = "" # User left the field blank
if not customer_email:
print("Email address is required. Cannot process order.")
else:
send_confirmation(customer_email)
This is cleaner than writing if customer_email == "". The not customer_email version works correctly for None, "", and any other falsy value, making it robust to different data sources.
Checking whether a list has contents:
pending_approvals = [] # Empty queue
if pending_approvals:
print(f"Processing {len(pending_approvals)} pending approvals.")
else:
print("No pending approvals. Queue is clear.")
Again, if pending_approvals is more idiomatic than if len(pending_approvals) > 0.
Handling optional configuration:
override_discount = None # No override set
final_discount = override_discount if override_discount else standard_discount
Here, if override_discount is None (or 0, or any other falsy value), standard_discount is used. This is a ternary expression — we will cover it fully in Section 4.8.
Truthiness Pitfalls
Truthiness is powerful but requires care in one situation: when zero or an empty collection is a meaningful value, not an error state.
discount_rate = 0.0 # A legitimate "no discount" value
# WRONG — this incorrectly treats 0.0 as "no rate provided"
if not discount_rate:
print("Discount rate not set.") # This fires even when rate is intentionally 0!
# CORRECT — explicitly check for None
if discount_rate is None:
print("Discount rate not set.")
The distinction: use truthiness when 0 and None should be treated the same (both mean "nothing"). Use explicit is None checks when zero is a valid, meaningful value.
4.7 match/case: Clean Multi-Branch Logic (Python 3.10+)
Python 3.10 introduced the match statement, sometimes called "structural pattern matching." For business programmers, its most practical application is replacing long if/elif chains where you are comparing a single value against multiple possibilities.
Basic Syntax
match some_value:
case "option_a":
# runs if some_value == "option_a"
case "option_b":
# runs if some_value == "option_b"
case _:
# the wildcard — runs if nothing else matched
# equivalent to the "else" in an if/elif/else chain
Business Example: Pricing by Subscription Plan
Maya Reyes is building a billing script for a SaaS client. The client has four subscription plans with different pricing rules:
subscription_plan = "Professional"
base_seats = 5
additional_seats = 3
match subscription_plan:
case "Starter":
price_per_seat = 12.00
max_seats = 5
support_tier = "Email only"
case "Professional":
price_per_seat = 22.00
max_seats = 25
support_tier = "Email + Chat"
case "Business":
price_per_seat = 38.00
max_seats = 100
support_tier = "Priority phone + Email"
case "Enterprise":
price_per_seat = 55.00
max_seats = None # Unlimited
support_tier = "Dedicated account manager"
case _:
print(f"Unknown plan: {subscription_plan}. Defaulting to Starter pricing.")
price_per_seat = 12.00
max_seats = 5
support_tier = "Email only"
total_seats = base_seats + additional_seats
monthly_total = total_seats * price_per_seat
print(f"Plan: {subscription_plan}")
print(f"Seats: {total_seats} × ${price_per_seat:.2f} = ${monthly_total:.2f}/month")
print(f"Support: {support_tier}")
Output:
Plan: Professional
Seats: 8 × $22.00 = $176.00/month
Support: Email + Chat
match/case with Multiple Values Per Branch
You can match against several values in a single case using the | (pipe) operator:
shipping_region = "Southeast"
match shipping_region:
case "Northeast" | "Southeast":
fulfillment_center = "Atlanta Distribution Hub"
standard_days = 2
case "Midwest":
fulfillment_center = "Chicago Logistics Center"
standard_days = 3
case "Southwest" | "West Coast":
fulfillment_center = "Phoenix Fulfillment Center"
standard_days = 3
case "International":
fulfillment_center = "Miami International Freight"
standard_days = 10
case _:
fulfillment_center = "Default (East Coast)"
standard_days = 5
print(f"{shipping_region} ships from {fulfillment_center} in {standard_days} days.")
When to Use match/case vs. if/elif
Use match/case when:
- You are comparing a single variable against a list of specific values
- The branches are roughly parallel in structure (each does a similar kind of thing)
- You have four or more branches (fewer than that, if/elif is fine)
Use if/elif when:
- Your conditions involve comparisons (>, <, >=)
- Your conditions involve multiple variables combined with and/or
- Your branches are fundamentally different in nature
match/case is not a replacement for all conditional logic — it is a specialized tool that makes a specific pattern cleaner and more readable.
4.8 Short-Circuit Evaluation in Conditionals
We introduced and and or in Chapter 3. In the context of conditionals, understanding how Python evaluates them is important for both correctness and efficiency.
How and Short-Circuits
With and, Python evaluates left to right and stops as soon as it finds a False value:
customer_is_active = False
credit_limit = 10_000
# Python does NOT evaluate credit_limit > 5000 because it already knows
# the overall result is False (active customers required first).
if customer_is_active and credit_limit > 5_000:
issue_premium_card()
This matters in practice when the second condition is expensive (a database call, for example) or when the second condition could crash if the first condition is false:
customer_data = None # Customer not found in database
# UNSAFE — Python would crash with AttributeError trying customer_data.tier
# if customer_data is None, because None has no .tier attribute.
if customer_data.tier == "Gold" and customer_data.balance > 0:
apply_gold_perks()
# SAFE — short-circuit saves us. If customer_data is None (Falsy),
# Python never evaluates customer_data.tier.
if customer_data and customer_data.tier == "Gold" and customer_data.balance > 0:
apply_gold_perks()
This pattern — checking that an object exists before accessing its attributes — is one of the most common and practical uses of short-circuit evaluation.
How or Short-Circuits
With or, Python stops as soon as it finds a True value:
preferred_email = "" # Customer's preferred email is blank
account_email = "j.doe@example.com"
# Uses preferred_email if it is truthy; falls back to account_email otherwise.
contact_email = preferred_email or account_email
print(contact_email) # j.doe@example.com
or for defaults is a compact and idiomatic Python pattern. It says: "use the first option if it exists and is non-empty; otherwise use the second." You will see this constantly in professional Python code.
not for Inverting Conditions
The not operator flips a boolean:
payment_method_on_file = False
if not payment_method_on_file:
print("Please add a payment method before placing your order.")
not pairs well with in and is:
approved_regions = ["Northeast", "Midwest", "Southeast", "West Coast"]
order_region = "International"
if order_region not in approved_regions:
print(f"Orders to {order_region} require special handling.")
response = None
if response is not None:
process_response(response)
4.9 The Ternary Expression (Inline if)
Sometimes a decision is simple enough that a full if/else block feels like overkill. Python's ternary expression (also called the conditional expression or inline if) lets you write a two-branch decision in a single line:
value_if_true if condition else value_if_false
This is most useful for assigning a variable based on a simple condition:
order_total = 650.00
free_shipping_threshold = 500.00
shipping_label = "Free Shipping" if order_total >= free_shipping_threshold else "Standard Shipping"
print(shipping_label) # Free Shipping
Or in an f-string:
is_vip = True
print(f"Thank you for your order, {'VIP ' if is_vip else ''}customer.")
# Thank you for your order, VIP customer.
When Not to Use the Ternary
The ternary is a readability tool for simple cases. Do not use it when:
- Either branch requires multiple statements (that is what
if/elseblocks are for) - The condition itself is long and complex
- You are nesting ternaries inside each other (this is almost never a good idea)
# BAD — this is unreadable
result = "A" if x > 10 else "B" if x > 5 else "C" if x > 0 else "D"
# GOOD — use if/elif/else for anything with more than two branches
if x > 10:
result = "A"
elif x > 5:
result = "B"
elif x > 0:
result = "C"
else:
result = "D"
The test: if you have to read the ternary more than once to understand it, use a full if/else block.
4.10 Defensive Patterns: Validating Inputs Before Making Decisions
Business programs receive data from the real world: user forms, API responses, spreadsheet imports, database queries. The real world is messy. Inputs can be missing, malformed, out of range, or the wrong type entirely.
Defensive programming means checking your inputs before you act on them. The alternative — assuming inputs are always valid — leads to programs that crash unpredictably on real data, which erodes trust and causes real business harm.
Input Validation: The Guard Pattern
Structure your functions to validate first, then act:
def apply_discount(order_total: float, discount_code: str) -> float:
"""
Apply a promotional discount code to an order total.
Returns the discounted total, or the original total if the code is invalid.
"""
# Guard 1: Ensure the order total makes sense
if order_total <= 0:
print(f"Warning: Order total must be positive (received {order_total}). No discount applied.")
return order_total
# Guard 2: Ensure a discount code was actually provided
if not discount_code:
print("No discount code provided.")
return order_total
# Guard 3: Normalize — handle different capitalizations
normalized_code = discount_code.strip().upper()
# Now we can safely apply business logic
valid_codes = {
"SAVE10": 0.10,
"SAVE20": 0.20,
"VIPONLY": 0.25,
}
discount_rate = valid_codes.get(normalized_code, None)
if discount_rate is None:
print(f"Discount code '{normalized_code}' is not valid or has expired.")
return order_total
discounted_total = order_total * (1 - discount_rate)
print(f"Code '{normalized_code}' applied: {discount_rate * 100:.0f}% off. "
f"New total: ${discounted_total:.2f}")
return discounted_total
Notice that the guards are at the top of the function. Each one catches a problem and returns early, so the business logic at the end can assume clean inputs. This is the guard-clause style we discussed in Section 4.5, applied to data validation.
Type Checking for Robustness
Sometimes you need to verify that a value is the right type before operating on it:
def calculate_late_fee(days_overdue, daily_rate=2.50):
"""Calculate the late fee for an overdue invoice."""
# isinstance() checks the type without crashing if the type is wrong
if not isinstance(days_overdue, (int, float)):
print(f"Error: days_overdue must be a number (got {type(days_overdue).__name__}).")
return 0.0
if days_overdue < 0:
print("Warning: days_overdue cannot be negative. Using 0.")
days_overdue = 0
return days_overdue * daily_rate
The Principle of Explicit Over Implicit
Python's philosophy (available by running import this in a Python terminal) includes "explicit is better than implicit." In terms of validation, this means: if you are making an assumption about your data, write a check that enforces it. Do not silently hope the data is well-formed. When the assumption is violated in production, your explicit check will give you a clear error message instead of a mysterious crash three function calls later.
4.11 Decision Examples: Pricing, Credit Checks, Order Routing
Let us work through three complete business decision scenarios, each illustrating different aspects of control flow.
Scenario A: Tiered Pricing with Promotional Override
Sandra is building a pricing engine that applies Acme's standard tier pricing, but allows regional sales managers to provide an override rate during promotions:
def determine_unit_price(
base_price: float,
customer_tier: str,
override_price: float | None = None,
is_promotional_period: bool = False,
) -> tuple[float, str]:
"""
Determine the unit price for a product.
Returns a tuple of (final_price, explanation).
"""
# Override always takes precedence if provided and valid
if override_price is not None and override_price > 0:
return override_price, f"Manager override: ${override_price:.2f}"
# Promotional period discount (applies before tier discount)
if is_promotional_period:
promo_price = base_price * 0.85
return promo_price, f"Promotional price (15% off): ${promo_price:.2f}"
# Standard tier pricing
tier_multipliers = {
"Gold": 0.82, # 18% off list
"Silver": 0.88, # 12% off list
"Bronze": 0.94, # 6% off list
}
multiplier = tier_multipliers.get(customer_tier, 1.00) # 1.00 = full list price
tier_price = base_price * multiplier
if multiplier < 1.00:
explanation = f"{customer_tier} tier price ({(1 - multiplier) * 100:.0f}% off): ${tier_price:.2f}"
else:
explanation = f"List price (no tier discount): ${tier_price:.2f}"
return tier_price, explanation
# Test it
scenarios = [
(100.00, "Gold", None, False),
(100.00, "Silver", None, True), # Promotional period
(100.00, "Bronze", 79.99, False), # Manager override
(100.00, "New", None, False), # Unrecognized tier
]
for base, tier, override, promo in scenarios:
price, note = determine_unit_price(base, tier, override, promo)
print(f"Base: ${base:.2f} | Tier: {tier:<8} | → {note}")
Output:
Base: $100.00 | Tier: Gold | → Gold tier price (18% off): $82.00
Base: $100.00 | Tier: Silver | → Promotional price (15% off): $85.00
Base: $100.00 | Tier: Bronze | → Manager override: $79.99
Base: $100.00 | Tier: New | → List price (no tier discount): $100.00
Scenario B: Credit Limit Check
Priya is checking whether a customer's requested credit limit increase is within the auto-approval range:
def evaluate_credit_request(
customer_name: str,
current_limit: float,
requested_limit: float,
annual_revenue: float,
months_as_customer: int,
late_payments_last_12mo: int,
) -> str:
"""
Evaluate a credit limit change request.
Auto-approves within policy, flags borderline cases, declines clear violations.
"""
# Policy: must have been a customer for at least 6 months
if months_as_customer < 6:
return f"DECLINED: {customer_name} — account age ({months_as_customer} months) below 6-month minimum."
# Policy: no more than 2 late payments in last 12 months for any approval
if late_payments_last_12mo > 2:
return f"DECLINED: {customer_name} — {late_payments_last_12mo} late payments in last 12 months exceeds maximum (2)."
# Policy: requested limit cannot exceed 30% of annual revenue
max_allowable_limit = annual_revenue * 0.30
if requested_limit > max_allowable_limit:
return (
f"DECLINED: {customer_name} — requested limit (${requested_limit:,.0f}) "
f"exceeds 30% of annual revenue (${max_allowable_limit:,.0f})."
)
# Policy: increases over 25% of current limit require manual review
increase_pct = (requested_limit - current_limit) / current_limit
if increase_pct > 0.25:
return (
f"REVIEW: {customer_name} — requested {increase_pct * 100:.1f}% increase "
f"(${current_limit:,.0f} → ${requested_limit:,.0f}) exceeds 25% auto-approve threshold."
)
# Borderline: 1-2 late payments = approved but flagged
if late_payments_last_12mo > 0:
return (
f"APPROVED WITH NOTE: {customer_name} — credit limit increased to ${requested_limit:,.0f}. "
f"Note: {late_payments_last_12mo} late payment(s) on record."
)
# All clear — auto-approve
return f"AUTO-APPROVED: {customer_name} — credit limit increased to ${requested_limit:,.0f}."
Scenario C: Order Routing Summary
(See the full order_routing.py in the code/ directory for the complete implementation. The key idea: match/case handles region-based warehouse selection cleanly, while if/elif handles weight and priority tiers because those involve ranges rather than exact values.)
4.12 A Complete Order Processing Engine
Let us put everything from this chapter together into one coherent example. This order processor handles incoming orders from Acme's e-commerce channel, applying validation, pricing, discounts, shipping calculation, and fraud flags in sequence.
"""
order_processor.py — Acme Corp E-Commerce Order Processing
This module processes a single customer order through all decision stages:
1. Input validation
2. Customer verification
3. Pricing and discount calculation
4. Shipping method selection
5. Fraud and risk flagging
6. Final decision (process / hold / reject)
"""
from datetime import date
def process_order(order: dict) -> dict:
"""
Process a single order dictionary through Acme's fulfillment decision pipeline.
Args:
order: A dictionary with keys: customer_id, customer_tier, region,
items (list of dicts with 'price' and 'qty'), payment_method,
is_new_customer.
Returns:
A result dictionary with: status, message, subtotal, discount,
shipping_cost, total, flags.
"""
flags = []
result = {"status": None, "message": "", "flags": flags}
# -----------------------------------------------------------------------
# Stage 1: Validate the order structure
# -----------------------------------------------------------------------
required_fields = ["customer_id", "customer_tier", "region", "items", "payment_method"]
for field in required_fields:
if field not in order or not order[field]:
result["status"] = "REJECTED"
result["message"] = f"Missing required field: '{field}'."
return result
if not isinstance(order["items"], list) or len(order["items"]) == 0:
result["status"] = "REJECTED"
result["message"] = "Order must contain at least one item."
return result
# -----------------------------------------------------------------------
# Stage 2: Calculate subtotal
# -----------------------------------------------------------------------
subtotal = 0.0
for item in order["items"]:
# Defensive: skip items with missing price or quantity
if "price" not in item or "qty" not in item:
flags.append("Item missing price or quantity — skipped.")
continue
if item["price"] <= 0 or item["qty"] <= 0:
flags.append(f"Item with invalid price/qty skipped: {item}")
continue
subtotal += item["price"] * item["qty"]
if subtotal <= 0:
result["status"] = "REJECTED"
result["message"] = "No valid items in order after validation."
return result
result["subtotal"] = subtotal
# -----------------------------------------------------------------------
# Stage 3: Apply tier discount
# -----------------------------------------------------------------------
tier_discounts = {"Gold": 0.15, "Silver": 0.10, "Bronze": 0.05}
tier = order["customer_tier"]
discount_rate = tier_discounts.get(tier, 0.0)
if discount_rate == 0.0 and tier not in tier_discounts:
flags.append(f"Unrecognized tier '{tier}' — no discount applied.")
discount_amount = subtotal * discount_rate
discounted_subtotal = subtotal - discount_amount
result["discount"] = discount_amount
# -----------------------------------------------------------------------
# Stage 4: Shipping cost
# -----------------------------------------------------------------------
region = order["region"]
match region:
case "Northeast" | "Southeast" | "Midwest":
base_shipping = 8.99
case "Southwest" | "West Coast":
base_shipping = 12.99
case "International":
base_shipping = 45.00
case _:
base_shipping = 15.00
flags.append(f"Unknown region '{region}' — default shipping rate applied.")
# Free shipping for Gold customers on orders over $500 (post-discount)
if tier == "Gold" and discounted_subtotal >= 500:
shipping_cost = 0.00
flags.append("Gold customer + order >= $500: free shipping applied.")
else:
shipping_cost = base_shipping
result["shipping_cost"] = shipping_cost
# -----------------------------------------------------------------------
# Stage 5: Fraud and risk flags
# -----------------------------------------------------------------------
total = discounted_subtotal + shipping_cost
result["total"] = total
# High-value order from new customer — flag for review
if order.get("is_new_customer") and total > 2_000:
flags.append(f"New customer, high-value order (${total:.2f}) — fraud review triggered.")
# Suspicious: International + high value
if region == "International" and total > 5_000:
flags.append("International order over $5,000 — requires compliance review.")
# Payment method risk
if order.get("payment_method") == "wire_transfer" and total > 10_000:
flags.append("Wire transfer + order >$10,000 — finance approval required.")
# -----------------------------------------------------------------------
# Stage 6: Final routing decision
# -----------------------------------------------------------------------
# Certain flag combinations are hard holds
hard_hold_keywords = ["fraud review", "compliance review", "finance approval"]
is_hard_hold = any(
keyword in flag
for flag in flags
for keyword in hard_hold_keywords
)
if is_hard_hold:
result["status"] = "HOLD"
result["message"] = "Order held pending manual review. See flags."
else:
result["status"] = "APPROVED"
result["message"] = "Order approved and queued for fulfillment."
return result
# --- Demo ---
if __name__ == "__main__":
test_orders = [
{
"customer_id": "C-4821",
"customer_tier": "Gold",
"region": "Northeast",
"items": [{"price": 299.99, "qty": 2}, {"price": 49.99, "qty": 3}],
"payment_method": "credit_card",
"is_new_customer": False,
},
{
"customer_id": "C-NEW1",
"customer_tier": "Bronze",
"region": "International",
"items": [{"price": 1499.00, "qty": 4}],
"payment_method": "wire_transfer",
"is_new_customer": True,
},
{
"customer_id": "C-0042",
"customer_tier": "Silver",
"region": "West Coast",
"items": [], # Empty order — should be rejected
"payment_method": "credit_card",
"is_new_customer": False,
},
]
for i, order in enumerate(test_orders, 1):
print(f"\n{'=' * 55}")
print(f" ORDER {i}: Customer {order['customer_id']}")
print(f"{'=' * 55}")
result = process_order(order)
print(f" Status : {result['status']}")
print(f" Message : {result['message']}")
if "subtotal" in result:
print(f" Subtotal: ${result.get('subtotal', 0):.2f}")
print(f" Discount: -${result.get('discount', 0):.2f}")
print(f" Shipping: ${result.get('shipping_cost', 0):.2f}")
print(f" TOTAL : ${result.get('total', 0):.2f}")
if result["flags"]:
print(" Flags:")
for flag in result["flags"]:
print(f" • {flag}")
This engine demonstrates every concept from the chapter in a unified workflow: validation guards, if/elif/else for discounts, match/case for regional shipping, truthiness checks for missing data, and short-circuit evaluation in the hard-hold detection.
4.13 Chapter Summary
Control flow is how programs think. The skills in this chapter — if/elif/else, match/case, truthiness, short-circuit evaluation, ternary expressions, and defensive validation — are the tools you will reach for every time you need a program to make a decision.
Here is a quick reference for choosing the right tool:
| Situation | Best Tool |
|---|---|
| One condition, one action | if |
| Two possible outcomes | if/else |
| Three or more branches with conditions/ranges | if/elif/else |
| Matching one variable against many specific values | match/case |
| Simple two-value assignment | Ternary expression |
| Default value when something might be missing | or short-circuit |
| Checking object exists before accessing its properties | and short-circuit |
| Validating inputs before business logic | Guard clauses |
In the next chapter, we will learn how to repeat actions — introducing loops, which let you apply these same decision patterns across thousands of records automatically.
Key Terms
Conditional expression (ternary): A single-line if/else that produces one of two values based on a condition.
Control flow: The order in which Python executes statements. Conditionals and loops both change control flow.
Falsy: A value that Python treats as False in a boolean context. Includes 0, None, "", [], {}, and False itself.
Guard clause: An early-exit condition at the top of a function that rejects invalid or disqualifying inputs before the main logic runs.
match/case: Python 3.10+ syntax for pattern matching — comparing a value against a list of patterns and running the matching branch.
Short-circuit evaluation: Python's behavior of stopping evaluation of and/or expressions as soon as the result is determined.
Truthiness: Python's system of treating any value as either truthy or falsy in a boolean context, regardless of its type.