LexQLexQ
Back to blog
pricing logicbusiness rulesrule enginesimulationdecision operations platform

Your If-Else Pricing Logic Will Break. Here's What to Do Before It Does.

Refactoring your pricing if-else block doesn't fix the underlying problem. The structural answer is rules-as-data plus simulation — not better class hierarchies.

Sanghyun Park·May 12, 202611 min read

The 30-line function that now runs your pricing

It usually starts as a sketch on a Notion page. A flat fee. A 10% discount for returning customers. Free shipping above $50. The first version of PricingService.calculate() is maybe 30 lines. The PM is happy. The tests are green. Nobody worries about it.

Eighteen months later, the same file looks like this:

public BigDecimal calculate(Order order, Customer customer) {
    BigDecimal subtotal = order.getSubtotal();
    BigDecimal discount = BigDecimal.ZERO;

    if (customer.getTier() == Tier.PLATINUM) {
        if (order.getItemCount() >= 3) {
            discount = subtotal.multiply(new BigDecimal("0.15"));
        } else if (subtotal.compareTo(new BigDecimal("200")) >= 0) {
            discount = subtotal.multiply(new BigDecimal("0.12"));
        } else {
            discount = subtotal.multiply(new BigDecimal("0.10"));
        }
    } else if (customer.getTier() == Tier.GOLD) {
        if (order.containsCategory("ELECTRONICS")
                && !order.getCreatedAt().isBefore(Instant.parse("2026-04-01T00:00:00Z"))
                && !order.getCreatedAt().isAfter(Instant.parse("2026-04-30T23:59:59Z"))) {
            discount = subtotal.multiply(new BigDecimal("0.08"));
        } else if (customer.getLifetimeValue().compareTo(new BigDecimal("5000")) >= 0) {
            discount = subtotal.multiply(new BigDecimal("0.10"));
        }
    }

    if (order.getPromoCode() != null) {
        if (order.getPromoCode().equals("WELCOME20") && customer.isFirstOrder()) {
            BigDecimal extra = subtotal.multiply(new BigDecimal("0.20"));
            if (extra.compareTo(discount) > 0) {
                discount = extra; // not stackable with tier discount
            }
        } else if (order.getPromoCode().equals("VIP15") && customer.getTier() == Tier.PLATINUM) {
            discount = discount.add(subtotal.multiply(new BigDecimal("0.15")));
            // stackable on top of tier
        }
    }
    // ... and 200 more lines
}

This is not bad code. The original engineer knew what they were doing. Every branch was added in response to a real, documented business decision. Every condition made sense the day it was written.

But the file now describes thirteen overlapping pricing dimensions, has six branches that nobody is certain are still reachable, and has a single test file that covers nine specific orders. Every change to it is a small act of fear.

Pricing logic decays faster than almost any other business code, for one reason: it is the only kind of logic where a missed condition shows up immediately as a revenue line item. Eligibility bugs get caught in QA. Pricing bugs get caught in a CFO email on Monday morning.

Why refactoring won't save you

The standard advice for code like this is to refactor. Apply the Strategy pattern. Break each discount into a DiscountRule class. Order them in a list. Iterate.

The code becomes cleaner. The cyclomatic complexity score drops. The lead developer exhales.

Six months later, the same problem exists with extra layers.

Here is why. Refactoring solves readability. It does not solve any of the three things that actually cost money.

Every rule change still goes through a deploy. Marketing wants to raise the free-shipping threshold from $50 to $75 for a two-week experiment. With a Strategy pattern, that is still a pull request, a code review, a staging deploy, a production deploy, and a rollback plan. The unit of change did not shrink — it just moved into a smaller file.

The blast radius of a change is still invisible until production. Refactoring tells you what a single rule does. It does not tell you what happens when you change the threshold on rule #4 and rule #11 silently starts winning for 3% of your traffic. Pricing breaks at the interaction between rules, and the existing code structure does not make those interactions observable.

The audit answer is still "let me check git blame." When a customer service rep asks "why did this customer get 23% off?", the answer in a refactored codebase is still "let me trace through the Strategy chain in a debugger." The decision is not self-explaining at the moment a human asks the question.

Refactoring is real work, and it is not nothing. But the if-else collapse is not just an implementation issue — it is a symptom of a deeper modeling mistake. Pricing rules are being treated as code, when they are actually data with behavior.

The first step: rules become data

The structural fix is not a better class hierarchy. It is to move the rules out of the codebase entirely.

A pricing rule, expressed as data, has four parts:

  • A condition (e.g. loyalty_tier == 'PLATINUM' && purchase_subtotal_usd >= 500)
  • An action (e.g. subtract 12% from purchase_subtotal_usd)
  • A priority (which rule wins when multiple match)
  • A version (when did this rule come into effect, who added it, why)

When rules are data, the application code shrinks to one function: gather the facts, call the rule engine, apply the result. The application stops knowing which customer gets which discount. It only knows how to ask.

public BigDecimal calculate(Order order, Customer customer) {
    Map<String, Object> facts = Map.of(
        "purchase_subtotal_usd", order.getSubtotal(),
        "loyalty_tier", customer.getTier().name(),
        "cart_category_tags", order.getCategoryTags(),
        "customer_ltv_usd", customer.getLifetimeValue(),
        "loyalty_credit_usd", customer.getLoyaltyCredit(),
        "is_first_purchase", customer.isFirstOrder(),
        "applied_promo_code", order.getPromoCode(),
        // tenant-required system facts
        "user_id", customer.getId(),
        "payment_amount", order.getPaymentAmount()
    );

    PolicyResult result = lexq.execute("us-pricing-v1", facts);
    return (BigDecimal) result.mutatedFacts().get("purchase_subtotal_usd");
}

The 200-line calculate() becomes ten lines. Everything else lives somewhere editable — a console, a CLI, an API. PMs and ops can read the active ruleset without opening IntelliJ.

This is the first step. It is necessary, but it is not enough.

The second step: verify before you deploy

Moving rules out of code creates a new risk. The 200-line if-else block at least had a CI pipeline and code review catching the obvious breakage. A rule edited in a console at 3 PM goes live and starts affecting real orders within seconds.

You cannot get away with "I think this looks right" when the next 4,000 orders are about to apply your edit.

The discipline that production code has — version control, code review, staging environments, integration tests — has to be ported to rules. The part that does not port is testing against synthetic data. Pricing rules are too interdependent for that. The only test that means anything is: run the new rule against your last 30 days of real orders, and tell me exactly what would have changed.

This is what Impact Simulation does. A simulation API takes a candidate rule version, points it at a slice of historical traffic, and returns a structured diff. Here is what the LexQ response looks like for a single order — a $600 cart from a Platinum customer where eight rules are evaluated and three are selected:

{
    "result": "SUCCESS",
    "data": {
        "inputFacts": {
            "applied_promo_code": "",
            "cart_category_tags": "electronics,accessories",
            "user_id": "demo_user",
            "is_first_purchase": false,
            "loyalty_tier": "PLATINUM",
            "payment_amount": 600,
            "loyalty_credit_usd": 0,
            "customer_ltv_usd": 8200,
            "purchase_subtotal_usd": 600
        },
        "mutatedFacts": {
            "purchase_subtotal_usd": 503,
            "loyalty_credit_usd": 25
        },
        "generatedVariables": {
            "loyalty_credit_usd__delta": 25,
            "purchase_subtotal_usd__delta": -97
        },
        "executionTraces": [
            {
                "ruleName": "Promo: WELCOME20",
                "matched": false,
                "matchExpression": "applied_promo_code == 'WELCOME20'",
                "inputFacts": {
                    "purchase_subtotal_usd": 600,
                    "loyalty_tier": "PLATINUM",
                    "cart_category_tags": "electronics,accessories",
                    "customer_ltv_usd": 8200,
                    "loyalty_credit_usd": 0,
                    "applied_promo_code": "",
                    "is_first_purchase": false,
                    "user_id": "demo_user",
                    "payment_amount": 600
                },
                "generatedActions": [
                    {
                        "type": "MUTATE_FACT",
                        "parameters": {
                            "rate": 20,
                            "method": "PERCENTAGE",
                            "refVar": "purchase_subtotal_usd",
                            "operator": "SUB",
                            "rounding": { "mode": "HALF_UP", "scale": 2 }
                        }
                    }
                ]
            },
            {
                "ruleName": "Promo: VIP15",
                "matched": false,
                "matchExpression": "applied_promo_code == 'VIP15'",
                "inputFacts": {
                    "purchase_subtotal_usd": 600,
                    "loyalty_tier": "PLATINUM",
                    "cart_category_tags": "electronics,accessories",
                    "customer_ltv_usd": 8200,
                    "loyalty_credit_usd": 0,
                    "applied_promo_code": "",
                    "is_first_purchase": false,
                    "user_id": "demo_user",
                    "payment_amount": 600
                },
                "generatedActions": [
                    {
                        "type": "MUTATE_FACT",
                        "parameters": {
                            "rate": 15,
                            "method": "PERCENTAGE",
                            "refVar": "purchase_subtotal_usd",
                            "operator": "SUB",
                            "rounding": { "mode": "HALF_UP", "scale": 2 }
                        }
                    }
                ]
            },
            {
                "ruleName": "Tier: PLATINUM 12%",
                "matched": true,
                "matchExpression": "loyalty_tier == 'PLATINUM'",
                "inputFacts": {
                    "applied_promo_code": "",
                    "cart_category_tags": "electronics,accessories",
                    "user_id": "demo_user",
                    "loyalty_tier": "PLATINUM",
                    "loyalty_credit_usd": 0,
                    "purchase_subtotal_usd": 600,
                    "payment_amount": 600,
                    "customer_ltv_usd": 8200,
                    "is_first_purchase": false
                },
                "generatedActions": [
                    {
                        "type": "MUTATE_FACT",
                        "parameters": {
                            "rate": 12,
                            "method": "PERCENTAGE",
                            "refVar": "purchase_subtotal_usd",
                            "operator": "SUB",
                            "rounding": { "mode": "HALF_UP", "scale": 2 }
                        }
                    }
                ]
            },
            {
                "ruleName": "Tier: GOLD 8%",
                "matched": false,
                "matchExpression": "loyalty_tier == 'GOLD'",
                "inputFacts": {
                    "purchase_subtotal_usd": 600,
                    "loyalty_tier": "PLATINUM",
                    "cart_category_tags": "electronics,accessories",
                    "customer_ltv_usd": 8200,
                    "loyalty_credit_usd": 0,
                    "applied_promo_code": "",
                    "is_first_purchase": false,
                    "user_id": "demo_user",
                    "payment_amount": 600
                },
                "generatedActions": [
                    {
                        "type": "MUTATE_FACT",
                        "parameters": {
                            "rate": 8,
                            "method": "PERCENTAGE",
                            "refVar": "purchase_subtotal_usd",
                            "operator": "SUB",
                            "rounding": { "mode": "HALF_UP", "scale": 2 }
                        }
                    }
                ]
            },
            {
                "ruleName": "Tier: SILVER 5%",
                "matched": false,
                "matchExpression": "loyalty_tier == 'SILVER'",
                "inputFacts": {
                    "purchase_subtotal_usd": 600,
                    "loyalty_tier": "PLATINUM",
                    "cart_category_tags": "electronics,accessories",
                    "customer_ltv_usd": 8200,
                    "loyalty_credit_usd": 0,
                    "applied_promo_code": "",
                    "is_first_purchase": false,
                    "user_id": "demo_user",
                    "payment_amount": 600
                },
                "generatedActions": [
                    {
                        "type": "MUTATE_FACT",
                        "parameters": {
                            "rate": 5,
                            "method": "PERCENTAGE",
                            "refVar": "purchase_subtotal_usd",
                            "operator": "SUB",
                            "rounding": { "mode": "HALF_UP", "scale": 2 }
                        }
                    }
                ]
            },
            {
                "ruleName": "First Purchase Bonus",
                "matched": false,
                "matchExpression": "is_first_purchase == true",
                "inputFacts": {
                    "purchase_subtotal_usd": 600,
                    "loyalty_tier": "PLATINUM",
                    "cart_category_tags": "electronics,accessories",
                    "customer_ltv_usd": 8200,
                    "loyalty_credit_usd": 0,
                    "applied_promo_code": "",
                    "is_first_purchase": false,
                    "user_id": "demo_user",
                    "payment_amount": 600
                },
                "generatedActions": [
                    {
                        "type": "MUTATE_FACT",
                        "parameters": {
                            "value": 10,
                            "method": "AMOUNT",
                            "refVar": "purchase_subtotal_usd",
                            "operator": "SUB",
                            "rounding": { "mode": "HALF_UP", "scale": 2 }
                        }
                    }
                ]
            },
            {
                "ruleName": "Electronics ≥ $500",
                "matched": true,
                "matchExpression": "(cart_category_tags contains 'electronics') && (purchase_subtotal_usd >= 500)",
                "inputFacts": {
                    "applied_promo_code": "",
                    "cart_category_tags": "electronics,accessories",
                    "user_id": "demo_user",
                    "loyalty_tier": "PLATINUM",
                    "loyalty_credit_usd": 0,
                    "purchase_subtotal_usd": 600,
                    "payment_amount": 600,
                    "customer_ltv_usd": 8200,
                    "is_first_purchase": false
                },
                "generatedActions": [
                    {
                        "type": "MUTATE_FACT",
                        "parameters": {
                            "value": 25,
                            "method": "AMOUNT",
                            "refVar": "purchase_subtotal_usd",
                            "operator": "SUB",
                            "rounding": { "mode": "HALF_UP", "scale": 2 }
                        }
                    }
                ]
            },
            {
                "ruleName": "High LTV Reward",
                "matched": true,
                "matchExpression": "customer_ltv_usd > 5000",
                "inputFacts": {
                    "applied_promo_code": "",
                    "cart_category_tags": "electronics,accessories",
                    "user_id": "demo_user",
                    "loyalty_tier": "PLATINUM",
                    "loyalty_credit_usd": 0,
                    "purchase_subtotal_usd": 600,
                    "payment_amount": 600,
                    "customer_ltv_usd": 8200,
                    "is_first_purchase": false
                },
                "generatedActions": [
                    {
                        "type": "INCREMENT_FACT",
                        "parameters": {
                            "value": 25,
                            "method": "AMOUNT",
                            "rounding": { "mode": "HALF_UP", "scale": 2 },
                            "targetVar": "loyalty_credit_usd"
                        }
                    }
                ]
            }
        ],
        "decisionTraces": [
            {
                "ruleName": "Promo: WELCOME20",
                "status": "NO_MATCH",
                "reasonCode": "CONDITION_MISMATCH",
                "reasonDetail": null
            },
            {
                "ruleName": "Promo: VIP15",
                "status": "NO_MATCH",
                "reasonCode": "CONDITION_MISMATCH",
                "reasonDetail": null
            },
            {
                "ruleName": "Tier: PLATINUM 12%",
                "status": "SELECTED",
                "reasonCode": "FINAL_WINNER",
                "reasonDetail": null
            },
            {
                "ruleName": "Tier: GOLD 8%",
                "status": "NO_MATCH",
                "reasonCode": "CONDITION_MISMATCH",
                "reasonDetail": null
            },
            {
                "ruleName": "Tier: SILVER 5%",
                "status": "NO_MATCH",
                "reasonCode": "CONDITION_MISMATCH",
                "reasonDetail": null
            },
            {
                "ruleName": "First Purchase Bonus",
                "status": "NO_MATCH",
                "reasonCode": "CONDITION_MISMATCH",
                "reasonDetail": null
            },
            {
                "ruleName": "Electronics ≥ $500",
                "status": "SELECTED",
                "reasonCode": "FINAL_WINNER",
                "reasonDetail": null
            },
            {
                "ruleName": "High LTV Reward",
                "status": "SELECTED",
                "reasonCode": "FINAL_WINNER",
                "reasonDetail": null
            }
        ]
    }
}

Five layers of state, exposed deliberately. inputFacts is what came in. mutatedFacts is what the rules changed. generatedVariables carries the delta — the answer to "how much did this rule cost me on this order?" Run this across last month's traffic and the same __delta aggregation answers the same question at portfolio scale: would this rule change have cost us $42,000 last month? You get the number before deploying it. Not after.

executionTraces records every rule's evaluation — which match expression ran against which facts, whether it matched, and what action it would have generated. The five rules that did not match are still in the trace, which is the entire point: simulation only works if you can see what almost applied, not just what won. decisionTraces is one level up — which rules were selected and why. There is no Strategy chain to walk in a debugger. The decision explains itself.

This is the missing half of "rules as data." Without simulation, an externalized rule system just moves the if-else problem out of the codebase. Changes get faster, but the operational risk itself does not go away.

The rule engine itself is not the point. The point is whether changes can be validated against real production traffic before they reach it.

When you do not need this

The structural fix is not free. It adds a network call, an external system, and a learning curve. There are pricing models where it is the wrong choice.

Stay with hardcoded logic if:

  • Your price is a single number you change once a year ($99/month, period).
  • Your changes are uniform — "raise everything by 5%" — and never involve customer segmentation, time windows, or condition stacking. A SQL UPDATE is your refactor.
  • The rule logic is purely technical and externally specified (e.g. tax calculation that follows a published government table, where you do not want anyone editing it in a console).

The signal that you need rules-as-data is not the line count of the if-else block. It is the frequency and variety of pricing changes, and how many of them you are afraid to ship.

What to do tomorrow

You do not migrate the whole PricingService at once. Pick the part you are most afraid of — usually it is the promo-code branch or the tier-stacking section. Move just that into a rule engine. Run it in shadow mode against last month's orders. Compare the outputs to what the production code actually decided. Fix the gaps. Then cut over.

The if-else block does not disappear that day. But for the first time, the next pricing change does not require a deploy and a prayer.

Where LexQ fits

LexQ is the Decision Operations Platform with built-in Impact Simulation, designed for exactly this kind of change. Test rule changes on real production traffic. Understand every decision with full trace. Deploy with confidence.

Try LexQ's playground — no signup required


Related reading

Ready to move decisions out of your deploy pipeline?

Try LexQ free — no credit card required.

Start Free