14 min read

> "Every business policy is, at its core, a conditional statement. Python just lets you write it in a language a computer can enforce."

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, and else blocks 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:

  1. The if line must end with a colon (:).
  2. 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/else blocks 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.