# Low Level Design: Rule Engine

Source: https://www.techinterview.org/post/3233470922/lld-rule-engine/
Updated: 2026-04-18 · techinterview.org

## What Is a Rule Engine?

A configurable rule engine evaluates business logic defined as structured data rather than code. Rules are stored in a database, loaded at runtime, and can be updated and hot-reloaded without a deployment, enabling product and operations teams to change behavior independently of engineering.

## Rule Table Schema


```
Rule (
  id          UUID PRIMARY KEY,
  name        VARCHAR(255),
  priority    INT,             -- lower number = higher priority
  conditions  JSONB,           -- condition groups
  actions     JSONB,           -- actions to execute on match
  enabled     BOOLEAN,
  version     INT,             -- incremented on each update
  created_at  TIMESTAMPTZ
)
```


## Condition Schema

Each condition is a JSON object with three fields:


```
{
  "field":    "user.country",
  "operator": "eq",
  "value":    "US"
}
```

Supported operators:

  - Equality: `eq`, `neq`

  - Numeric: `gt`, `gte`, `lt`, `lte`

  - Set membership: `in`, `not_in`

  - String: `contains`, `starts_with`

  - Null check: `is_null`

Conditions are grouped: AND logic within a group, OR logic between groups.

## Action Schema


```
{
  "type":   "send_notification",
  "params": { "channel": "slack", "template": "fraud_alert" }
}
```

Supported action types:

  - `set_field` — write a value into the rule context

  - `add_tag` — append a tag to the context entity

  - `send_notification` — trigger a notification via configured channel

  - `call_webhook` — POST context data to an external URL

  - `block` — halt processing and return a blocked response

  - `route_to` — direct the entity to a specified queue or workflow

## Evaluation Algorithm


```
rules = load_rules_ordered_by_priority()

for rule in rules:
    if not rule.enabled:
        continue
    if evaluate_conditions(rule.conditions, context):
        execute_actions(rule.actions, context)
        if rule.stop_on_match:
            break   # first-match mode
        # else continue to next rule (chain mode)
```


## Field Resolution

Fields use dot-notation paths into the rule context, which is an arbitrary key-value map:


```
context = {
  "user": { "country": "DE", "age": 34 },
  "order": { "amount": 250.00, "currency": "EUR" }
}

resolve("order.amount", context)  ->  250.00
resolve("user.country", context)  ->  "DE"
```


## Hot Reload via Redis Pub/Sub

Rules are cached in application memory with a 30-second TTL. When a rule is created, updated, or deleted in the database, the admin service publishes an invalidation message to a Redis channel. All evaluator instances subscribe and flush their local cache immediately:


```
-- On rule update:
PUBLISH rule-invalidations "{"rule_id": "uuid"}"

-- Subscriber handler:
on message(channel, data):
    cache.delete(data.rule_id)
    -- next evaluation triggers fresh DB load
```


## Rule Versioning and Audit

Every update to a rule increments its `version` integer. Previous versions are retained in a `RuleVersion` audit table:


```
RuleVersion (
  rule_id     UUID,
  version     INT,
  conditions  JSONB,
  actions     JSONB,
  changed_by  UUID,
  changed_at  TIMESTAMPTZ,
  PRIMARY KEY (rule_id, version)
)
```


## Test Mode

Rules can be evaluated against a sample context without executing any actions. The evaluator returns which rules matched and which conditions passed or failed, enabling safe rule authoring before enabling in production.


```
POST /rules/evaluate-dry-run
{
  "context": { "user": { "country": "FR" }, "order": { "amount": 500 } }
}

Response:
{
  "matched_rules": ["uuid-1", "uuid-3"],
  "skipped_rules": ["uuid-2"],
  "actions_would_execute": [...]
}
```


## Key Design Considerations

  - Priority integers should be spaced (e.g., 100, 200, 300) to allow inserting rules between existing ones without renumbering.

  - The evaluator is stateless; all state lives in the context map passed by the caller.

  - Webhook action calls are made asynchronously via a job queue to avoid blocking the evaluation response.

  - Rule changes require a two-step publish: save to DB first, then publish invalidation, so subscribers always read a consistent state.

See also: [Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems](https://www.techinterview.org/post/3233460268/stripe-interview-guide-2026-process-bug-bash-round-and-payment-systems/)

See also: [Atlassian Interview Guide](https://www.techinterview.org/post/3233459919/atlassian-interview/)

See also: [Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering](https://www.techinterview.org/post/3233460275/scale-ai-interview-guide-2026-data-infrastructure-rlhf-pipelines-and-ml-engineering/)

See also: [Shopify Interview Guide](https://www.techinterview.org/post/3233459924/shopify-interview/)
