REUSABLE COMPONENT DESIGN / OBJECT DESIGN BRIEF

Rule engine design

A rule engine evaluates business rules (conditions and actions) against facts, separating frequently changing policy from application code.

AdvancedPhase 08 / Topic 5 of 7ResponsibilitiesCollaborationsExtensibility
01

Overview

A rule engine evaluates business rules (conditions and actions) against facts, separating frequently changing policy from application code. Examples: discounts (if cart > 100 and member, 10% off), fraud checks (if amount > 5,000 and new device, flag), eligibility, routing, and pricing. Rules have a condition, an action or outcome, and often a priority.

The LLD centers on a Rule interface (matches(facts), apply(facts)), composable conditions (AND, OR, NOT via the Composite or Specification pattern), a RuleEngine that evaluates rules in priority order with a strategy (first match, all matches), and optionally loading rules from configuration so non-engineers can change them.

A club's entry policy card

The bouncer checks a list: 'members enter free', 'guests over 21 pay 10', 'no entry after 2 AM'. Management can change the card without retraining the bouncer.

02

When to use it

  • Business rules change often and independently of code.
  • Many combinable conditions (discounts, eligibility, fraud).
  • Interview prompt: 'Design a discount or rule engine'.
03

Where it shows up in interviews

Policy engines

Recognize it when: discounts, fraud, eligibility, routing rules.

  • Design a discount engine
  • Design a fraud detection rule system
  • Design an insurance eligibility checker
04

Where it is used in real software

Drools

A Java rule engine (Rete algorithm) used in banking and insurance.

AWS IAM and OPA

Policy engines evaluate allow/deny rules against requests.

E-commerce promotions

Platforms like Shopify and Magento model promotions as configurable rules.

05

Key terms

Fact
Input data the rules evaluate (cart, user, transaction).
Condition / predicate
Boolean test over facts.
Action / outcome
What happens when a rule matches.
Specification pattern
Composable predicates with and/or/not.
Conflict resolution
Priority, first match, or combine all.
06

How it works, step by step

  1. 1
    Define facts

    A typed context object.

  2. 2
    Define conditions

    Small predicates, composable.

  3. 3
    Define rules

    Condition + outcome + priority.

  4. 4
    Choose evaluation strategy

    First match vs all matches; stackability rules.

  5. 5
    Externalize configuration

    Load rules from JSON or a DSL; validate on load.

07

Discount rules

Cart subtotal 150, member = true, coupon = none, strategy: best single discount

Step 1 / 4
RuleConditionMatches?Discount
Member 10%memberYes15
Big cartsubtotal >= 100Yes10 flat
Coupon SAVE20coupon == SAVE20No-
Result (best single)--15

NOWRule: Member 10% | Condition: member | Matches?: Yes | Discount: 15

Changing the strategy to 'stack all' would give 25; the rules themselves stay the same.

08

Implementation

type Facts = { subtotal: number; member: boolean; coupon?: string; itemCount: number }; // Specification pattern: composable conditionstype Condition = (f: Facts) => boolean;const and = (...cs: Condition[]): Condition => (f) => cs.every((c) => c(f));const or = (...cs: Condition[]): Condition => (f) => cs.some((c) => c(f));const not = (c: Condition): Condition => (f) => !c(f); type Rule = { name: string; priority: number; when: Condition; discount: (f: Facts) => number }; interface Strategy { combine(matches: { rule: Rule; amount: number }[]): number }const bestSingle: Strategy = { combine: (m) => Math.max(0, ...m.map((x) => x.amount)) };const stackAll: Strategy = { combine: (m) => m.reduce((s, x) => s + x.amount, 0) }; class RuleEngine {  constructor(private rules: Rule[], private strategy: Strategy) {}  evaluate(facts: Facts) {    const matches = [...this.rules]      .sort((a, b) => b.priority - a.priority)      .filter((r) => r.when(facts))      .map((r) => ({ rule: r, amount: r.discount(facts) }));    return { discount: Math.min(facts.subtotal, this.strategy.combine(matches)), applied: matches.map((m) => m.rule.name) };  }} const rules: Rule[] = [  { name: "member-10pct", priority: 2, when: (f) => f.member, discount: (f) => f.subtotal * 0.1 },  { name: "big-cart", priority: 1, when: and((f) => f.subtotal >= 100, not((f) => f.coupon === "SAVE20")), discount: () => 10 },  { name: "save20", priority: 3, when: or((f) => f.coupon === "SAVE20"), discount: () => 20 },]; new RuleEngine(rules, bestSingle).evaluate({ subtotal: 150, member: true, itemCount: 3 }); // { discount: 15, ... }
09

Complexity and performance

Naive evaluationO(rules x condition cost)

Fine for hundreds of rules.

Rete algorithmShares condition evaluation

For thousands of rules.

10

Trade-offs

Flexibility vs debuggability

Externalized rules empower business users but make behavior harder to trace; log which rules fired.

Code rules vs data rules

Rules in code are type-safe and tested; rules in config change without deploys but need validation and versioning.

11

Variants and related techniques

Decision tables

Rows of conditions and outcomes, easy for business users.

Scoring engines

Rules add points; thresholds decide outcomes (fraud scoring).

12

Common mistakes

  • Rules with hidden side effects.

    Fix: Keep conditions pure; perform actions after evaluation.

  • Undefined conflict resolution.

    Fix: Specify priority and combine strategy explicitly.

  • No audit of fired rules.

    Fix: Return and log which rules matched for explainability.

13

Interview questions

How would you design an extensible discount engine?

Rules with composable conditions (Specification pattern) and discount calculations, a priority order, and a pluggable combination strategy (best single, stack all, exclusive groups), with rules optionally loaded from validated configuration.

How do you make rules explainable?

Return the list of rules that matched with their contributions, log them with the decision, and version the rule set so decisions can be reproduced.

14

Practice problems

ProblemDifficultyWhat it trains
Discount engine with stackable rulesMediumStrategy for combining.
Fraud rules loaded from JSONHardValidation and versioning.