Fraud doesn’t wait for deployments.
At DoorDash, critical decisions – whether to allow an interaction, restrict a transaction, or require additional verification – must be made in real time. Rules drive every decision; for a long time, these rules lived inside application code. And that model worked – until it didn’t.
The problem that forced our hand
Fraud patterns began changing faster than we could ship out new code. Our strategy team could identify new attacks quickly, but turning those insights into action meant writing code, running tests, and redeploying services. Even small changes took days. But during that time, fraud losses would accumulate while excessively conservative rules sometimes blocked legitimate activity.
The issue wasn’t that we lacked signals or detection accuracy; it was that our ability to respond was fundamentally too slow. At DoorDash’s scale, that delay became unacceptable. By the time a rule reached production, fraudsters had already adapted to elude it. We didn’t need better rules; we needed a different way to ship them.
A turning point
Once we recognized the constraint, the direction became clear. Fraud logic didn’t need to be rewritten; it needed to be removed from the deployment cycle. The teams closest to fraud patterns already had the right instincts and context. What they lacked was a safe way to act on those insights without being forced to wait for engineering releases.
So, we changed the model. We decided to treat fraud rules not as code, but as configuration – something that could be authored, reviewed, tested, and rolled out independent of service deployments.
This decision came with non-negotiable requirements:
- Changes had to be safe by default
- Every decision had to be explainable and auditable
- New logic needed to be tested before affecting real users
The result: A real-time rules engine designed to let teams ship fraud decisions even faster than fraud evolves, without compromising reliability or correctness.
Rules engine in action: A checkout decision
To understand how the rules engine works, it helps to follow a single decision end-to-end.
Consider checkout. When a user places an order, DoorDash has milliseconds to decide whether to allow the transaction, flag it for review, or block it outright. This decision is high-risk, latency-sensitive, and irreversible once money changes hands.
Checkout is also the exact moment when we invoke the rules engine.
What follows is a deterministic, real-time evaluation pipeline that moves from context to logic to outcome without any service redeployments.
Checkpoints: Where decisions start
Every major business flow has a clear entry point where a decision must be made. In the rules engine, these entry points are referred to as checkpoints.
Checkout is one such checkpoint. As shown in Figure 1, when a request enters this checkpoint, we evaluate only the rules that apply to the checkout process. This scoping ensures fast decisions and avoids unintended interactions with other flows.

Each checkpoint owns its own rule set and rollout strategy, allowing teams to evolve logic independently while sharing the same underlying platform.
Building the context with facts
Before evaluating any rules, we gather the information needed to make a decision. This information is modeled as facts.
Facts represent everything the system can learn about the request, including such things as identifiers and derived risk signals. Rather than fetching data ad hoc, facts are defined as a directed acyclic graph (DAG), where deeper signals are built from simpler ones.
For example:
‘user identifier’ → ‘user profile’ → ‘user chargeback rate’
Engineers define how facts are fetched, transformed, and cached. Once defined, facts are registered centrally and become reusable across checkpoints. At checkout, we evaluate only the subset of facts required for that decision, keeping latency predictable even at scale.
Turning context into logic via rules
After facts are available, the engine can evaluate the set of rules associated with the checkout process. These rules use readable expressions that are validated on every change. Each rule describes how the system should respond under specific conditions, such as unusual account behavior. Individual rules may produce different outcomes, which we resolve deterministically into a single final effect.
To avoid blunt enforcement, rules support dimensions – filters that narrow applicability to specific populations. This allows teams to be precise about who or what a rule applies to and who it explicitly excludes. Crucially, rule definitions are decoupled from execution. Changing a rule does not require code changes or service deployments.
Shipping logic without breaking production
Shipping logic that impacts real users is inherently risky. A single, overly broad condition can affect thousands of legitimate users in seconds. For that reason, we use offline backtesting for validation and two runtime evaluation modes, Shadow and Live, with an experimentation framework controlling rollout into Live.
Backtesting against the past
Before a rule ever sees live traffic, teams can also backtest it against historical requests. Backtesting replays recorded facts through the same evaluation engine, producing a counterfactual: What decision would the rules engine have made if this rule had existed at that time? This allows teams to:
- Estimate the impact on fraud and false positives,
- Compare multiple rule variants before rollout, and
- Catch regressions introduced by fact or rule changes
Crucially, backtests use the same rule definitions and evaluation semantics as production, eliminating an entire class of “works in analysis but not in reality” failures.
Shadowing live traffic
In shadow mode, rules are evaluated on live traffic, but outcomes are not applied. From the request’s perspective, the rule does not exist. From the platform’s perspective, however, it still executes the full evaluation pipeline:
- Required facts are computed,
- Rule conditions are evaluated, and
- Effects are recorded
These shadow outcomes are written to our data lake alongside the actual production decision, allowing teams to answer such questions as:
- How often would this rule have been fired?
- Which user segments would it have affected?
- Does it overlap with existing rules in unexpected ways?
Because shadow evaluation runs on real traffic, it exposes edge cases and data quality issues that rarely show up in offline testing.
Experimenting to find the right fit
Once a rule has proven safe in shadow mode, the rules engine supports controlled experimentation. In experiment mode, multiple rule variants can be evaluated in parallel across statistically isolated cohorts. Each cohort experiences a different decision path, while the rest of the system remains unchanged. This enables teams to answer such higher-order questions as:
- Is a stricter threshold actually better?
- Does adding additional conditions reduce false positives?
- How does this rule interact with existing enforcement?
Because experimentation is built into the decision engine itself, teams don’t need to build custom routing, flagging, or logging infrastructure to compare outcomes. Figure 2 shows how a versioned decision progresses.

Enforcing with confidence
A rule is not promoted to live mode until it has been validated through shadow evaluation, backtesting, and/or experimentation. In live mode, the rule’s effects are applied directly to the request – blocking, flagging, or allowing the transaction in real time. Even then, enforcement remains controlled:
- Rules can be scoped to specific dimensions
- Rollout can be gradual
- Changes are fully auditable and reversible
This layered approach ensures that speed never comes at the cost of safety.
Acting on the decision
Once the evaluation is complete, the rules engine recommends an effect – the action that should be taken as a result of the decision. Using checkout as an example, these effects typically include allowing the order, flagging it for review, or blocking the transaction. Effects are composed of smaller, well-defined operations to improve observability and make outcomes explainable.
Architecture and scale
Our rules engine sits on the critical path of multiple high-volume business flows. That shapes every architectural decision. The system is designed to evaluate decisions in milliseconds while supporting a growing number of checkpoints, rules, and facts — without one use-case impacting another, as shown in Figure 3.

We support:
- Multiple checkpoints owned by different teams
- Thousands of active rules that are evaluated concurrently
- A large and evolving graph of reusable facts
Rule logic is isolated per checkpoint, while the infrastructure is shared underneath. This allows teams to evolve decision logic independently, without introducing cross-flow interactions.
Near real-time evaluation under load
Every decision is evaluated synchronously and in line with the request that triggered it. To keep latency predictable at scale, the engine:
- Only synchronously evaluates the facts required for the rules within the checkpoint
- Caches intermediate fact results aggressively; without this, the same high-latency fact would be recomputed dozens of times per request, blowing our p99 under peak load.
- Executes rule evaluation in parallel where possible
One checkpoint usually contains hundreds of facts — multiple DAG chains. The chain is only computed once per request, even if multiple rules can reference the different facts repeatedly during the evaluation. Intermediate facts can be shared across multiple DAG chains, and such facts will only be computed once per request. These optimizations allow us to operate reliably even at a very high rate of queries per second.
Multiple teams rely on the rules engine, but the platform’s multi-tenancy capabilities enforce strong isolation. Rule sets and configurations are scoped per tenant under the same checkpoint, while evaluation modes — shadow, experiment, and live — can run side-by-side. Misconfigurations in one domain do not cascade into others, and the configuration approval flow is independent per tenant.
Rule, checkpoint, effect, and experimentation configurations are stored in Apache Cassandra as the system of record. The underlying data model is optimized for:
- Read-heavy access patterns, with configurations reused across a large volume of decisions while meeting strict p99 latency service level objectives.
- Safe writes that prioritize versioning, approvals, and auditability.
Impact
The rules engine’s most visible change has been speed. At its peak, we currently serve around 10,000 requests per second, with about 15,000 facts hosted across multiple checkpoints. The system is designed to scale horizontally; capacity expands automatically with traffic, allowing us to handle significant growth without architectural changes
But the deeper impact has been on how decisions are made.
Faster, tighter responses
Previously, responding to a new fraud pattern meant waiting for the next deployment. Now, teams can create a rule, validate it in shadow mode, and roll it out safely in minutes. This dramatically shortens the gap between detection and mitigation, an essential shift when delays directly translate into losses.
- Incident mitigation: We reduced the time to safely roll out policy changes by over 80%, enabling rapid mitigation without waiting for service deployments.
- Deployment savings: By decoupling rule updates from application releases, we eliminated most deployment delays and reduced turnaround time for changes from hours—or in some cases days—to a fraction of that
Earlier intervention, lower cost
Faster iteration enables earlier action. Because rules can be tested and refined quickly, teams are able to intervene before fraud patterns scale. This has led to measurable reductions in fraud-related costs and fewer heavy-handed controls that negatively impact legitimate users.
Engineering focus where it matters
The rules engine has also changed how engineering time is spent. Engineers are no longer the critical path for routine rule changes or experiments. Instead, they can focus on:
- Scaling and hardening the platform
- Improving performance and reliability
- Expanding the system’s capabilities
This separation of concerns allows both engineering and strategy teams to operate more effectively.
Expanding beyond fraud
As the rules engine matured, it became clear that the underlying problem it solved wasn’t unique to fraud. Teams across DoorDash make high-stakes decisions in real-time, often under tight latency constraints and constantly evolving policies. Although the tension was first felt when dealing with fraud, the same pattern shows up elsewhere.
Teams in Trust & Safety, Logistics, and other domains face similar challenges that require they evaluate behavior and context in real-time, apply nuanced policies instead of hard-coded logic, and roll out changes safely without slowing product development. The rules engine platform we have created provides a common foundation for these decisions, including shared facts, configurable rules, built-in experimentation, and consistent effects. Consequently, we plan to evolve the platform into a general-purpose decisioning engine. Rather than each team building bespoke systems and workflows, this shared foundation could offer a reusable platform for expressing and enforcing decision logic across the company — reducing duplication, improving consistency, and allowing teams to focus on policy and intent, not infrastructure.
Looking forward
Fraud was just the beginning. Our long-term goal is simple: Any team at DoorDash that needs to make real-time, data-driven decisions should be able to do so safely, transparently, and independently.
