Skip to main content

Rule Based Discount Engine

In real systems, pricing logic is never:

if total > 100:
discount = 10

Real systems involve:

  • Multiple conditions
  • Overlapping rules
  • Priority ordering
  • Conflict resolution
  • Extensibility

This project teaches you how to design a deterministic rule engine.

Concept Overview

While watching, focus on:

  • Rule ordering importance
  • Conflict resolution strategies
  • Deterministic evaluation
  • Mapping-based decision models
  • Scalability of rule systems

Step 1 - Define Requirements

Our discount engine must:

  1. Accept cart total
  2. Accept customer type
  3. Accept coupon code
  4. Apply correct discount
  5. Prevent double-discount errors
  6. Be extensible for future rules
note

Rule systems must be deterministic. Same input → same output.

Step 2 - Define Basic Rules

Rules:

  • VIP customers → 20% discount
  • Orders above 100 → 10% discount
  • Coupon "SAVE5" → 5% discount
  • Employee → 30% discount
  • Default → No discount

Step 3 - Naive Implementation (Incorrect Design)

if customer_type == "VIP":
discount = 0.20
elif total > 100:
discount = 0.10
elif coupon == "SAVE5":
discount = 0.05

Problem:

  • Only one rule applied
  • No priority control
  • Hard to extend
  • Order-sensitive
  • Branch explosion risk

Step 4 - Deterministic Priority Model

Define rule priority:

  1. Employee
  2. VIP
  3. Coupon
  4. Cart Total

Explicit ordering matters.

Step 5 - Structured Implementation

def calculate_discount(total, customer_type, coupon):
if customer_type == "Employee":
return 0.30

if customer_type == "VIP":
return 0.20

if coupon == "SAVE5":
return 0.05

if total > 100:
return 0.10

return 0.0

This ensures deterministic order.

Step 6 - Apply Discount

def apply_discount(total, discount_rate):
return total * (1 - discount_rate)

Usage:

total = 150
discount = calculate_discount(total, "VIP", None)
final_price = apply_discount(total, discount)

print(f"Final price: {final_price}")

Step 7 - Prevent Overlapping Rule Conflicts

What if:

  • VIP
  • Coupon applied
  • Total > 100

Should discounts stack?

If stacking allowed:

def calculate_discount(total, customer_type, coupon):
discount = 0

if customer_type == "Employee":
discount += 0.30
elif customer_type == "VIP":
discount += 0.20

if coupon == "SAVE5":
discount += 0.05

if total > 100:
discount += 0.10

return min(discount, 0.50) # Cap at 50%

Now we control maximum exposure.

warning

Stacking without limits creates financial risk.

Step 8 - Convert to Rule Mapping (Scalable Model)

Avoid long if chains.

Use rule table.

RULES = [
("Employee", 0.30),
("VIP", 0.20),
]

def get_customer_discount(customer_type):
for rule_type, rate in RULES:
if customer_type == rule_type:
return rate
return 0

Extendable.

Add new roles easily.

Step 9 - Advanced Rule Engine (Data-Driven)

Define rules as functions.

def employee_rule(context):
if context["customer_type"] == "Employee":
return 0.30
return 0

def vip_rule(context):
if context["customer_type"] == "VIP":
return 0.20
return 0

def coupon_rule(context):
if context["coupon"] == "SAVE5":
return 0.05
return 0

def cart_rule(context):
if context["total"] > 100:
return 0.10
return 0

Engine:

RULE_FUNCTIONS = [
employee_rule,
vip_rule,
coupon_rule,
cart_rule,
]

def calculate_discount(context):
discount = 0
for rule in RULE_FUNCTIONS:
discount += rule(context)
return min(discount, 0.50)

Usage:

context = {
"total": 150,
"customer_type": "VIP",
"coupon": "SAVE5"
}

discount = calculate_discount(context)
final_price = apply_discount(context["total"], discount)

Now rule system is modular.

Step 10 - Decision Tree Visualization

Decision order:

Customer Type ↓ Coupon ↓ Cart Threshold ↓ Cap Check

Structured flow reduces ambiguity.

Step 11 - Add Early Exit Optimization

If stacking disabled:

for rule in RULE_FUNCTIONS:
discount = rule(context)
if discount > 0:
return discount

Stops evaluation early.

Performance-aware design.

tip

Cheap checks should run before expensive checks.

Step 12 - Detect Invalid Coupons

Add validation layer.

VALID_COUPONS = {"SAVE5"}

def coupon_rule(context):
coupon = context["coupon"]
if coupon and coupon not in VALID_COUPONS:
raise ValueError("Invalid coupon code")
if coupon == "SAVE5":
return 0.05
return 0

Fail fast.

Step 13 - Prevent Branch Explosion

Instead of:

if role == "VIP" and region == "EU":

Use mapping:

REGIONAL_DISCOUNTS = {
("VIP", "EU"): 0.25,
("VIP", "US"): 0.20,
}

Then:

rate = REGIONAL_DISCOUNTS.get((customer_type, region), 0)

Data-driven branching scales better.

Step 14 - Complexity Analysis

Each additional rule increases:

  • Execution paths
  • Risk of overlap
  • Testing combinations
  • Maintenance cost

Rule engines must be:

  • Ordered
  • Explicit
  • Bounded
  • Testable

Step 15 - Testing Strategy

Test cases:

  • VIP + coupon
  • Employee + coupon
  • Invalid coupon
  • High total no coupon
  • Edge case total = 100
  • Discount cap exceeded

Control flow systems must be exhaustively tested.

Common Mistakes

❌ Random rule ordering
❌ Hidden stacking
❌ Missing caps
❌ Deep nested if-elif chains
❌ No fallback
❌ Overlapping ambiguous conditions

Engineering Reflection

Before deployment, ask:

  • Is rule order deterministic?
  • Can new rules be added safely?
  • Are discount caps enforced?
  • Are conflicts handled?
  • Is branching readable?
  • Is evaluation cheap-to-expensive?

Rule systems grow.

Architecture must anticipate growth.

Final Insight

This project teaches:

  • Rule ordering
  • Decision-tree modeling
  • Conflict resolution
  • Data-driven branching
  • Guard-based validation
  • Performance-aware evaluation

Real e-commerce systems are rule engines.

Real pricing systems are decision trees.

Control flow is not about syntax.

It is about business logic architecture.

You just built one.

© 2026 EngineersOfAI. All rights reserved.