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 contextadd_tag— append a tag to the context entitysend_notification— trigger a notification via configured channelcall_webhook— POST context data to an external URLblock— halt processing and return a blocked responseroute_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.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “What is the benefit of a configurable rule engine over hardcoded logic?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A configurable rule engine stores business logic as data in a database rather than code. This allows product and operations teams to create, modify, and disable rules without engineering deployments, and supports hot-reload so changes take effect within seconds of saving.”
}
},
{
“@type”: “Question”,
“name”: “How are rule conditions structured in a rule engine?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Each condition specifies a field (using dot-notation like user.country), an operator (eq, gt, in, contains, is_null, etc.), and a value. Conditions are grouped with AND logic within a group and OR logic between groups, enabling complex boolean expressions without custom code.”
}
},
{
“@type”: “Question”,
“name”: “How does hot-reload work in a rule engine?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Rules are cached in application memory with a short TTL (typically 30 seconds). When a rule is updated in the database, the admin service publishes an invalidation message to a Redis pub/sub channel. All evaluator instances subscribe to this channel and immediately flush the affected rule from their local cache, so the next evaluation loads the fresh version.”
}
},
{
“@type”: “Question”,
“name”: “What is test mode in a rule engine?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Test mode (dry-run evaluation) runs the rule engine against a sample context without executing any actions. It returns which rules matched, which conditions passed or failed, and what actions would have been executed. This allows safe authoring and debugging of rules before enabling them in production.”
}
}
]
}
See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems
See also: Atlassian Interview Guide
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Shopify Interview Guide