algorithm - How to solve an arithmetic expression DAG?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
An arithmetic expression DAG (directed acyclic graph) represents shared subexpressions, unlike a tree where repeated expressions are duplicated. Solving it means evaluating nodes in dependency order so each subexpression is computed once. The standard approach is topological ordering followed by dynamic evaluation.
Core Sections
Represent nodes and edges
Each node is either:
- constant/variable leaf,
- operator node with input references.
Example node format:
n3 shared by n4 avoids recomputation.
Evaluate with topological sort
For DAG, topological order guarantees inputs are ready before a node.
Cycle detection
Expression graphs should be acyclic. If topological sort fails, input is invalid and should return an error.
Optimization opportunities
You can fold constant subgraphs and cache repeated evaluation across variable assignments where possible.
Numerical stability
For floating-point-heavy DAGs, operator ordering affects rounding error. Consider deterministic precision strategy in sensitive domains.
Common Pitfalls
- Treating DAG like tree and recomputing shared nodes repeatedly.
- Skipping cycle detection and hanging on invalid graphs.
- Evaluating nodes before dependencies are available.
- Ignoring division-by-zero and invalid operator domain checks.
- Overlooking precision drift in deep arithmetic chains.
Implementation Playbook
Define a clear intermediate representation and validation phase before evaluation. Validate node references, operator arity, and acyclicity first. Then benchmark evaluator with both random and adversarial graphs (high fan-out, deep chains, heavy sharing) to confirm performance and correctness under stress.
For production calculators/compilers, add tracing hooks that can output per-node values for debugging. This dramatically reduces incident resolution time when expression results differ from expectations. Keep deterministic test vectors in version control so algorithm refactors can be verified quickly.
Operational Readiness
Converting a technically correct implementation into a reliable production behavior requires explicit operational guardrails. Begin by defining success criteria in measurable terms: expected output shape, acceptable latency range, and acceptable failure rate under normal load. Then build a minimal verification harness that exercises the same code path with deterministic fixtures so behavioral drift is detected early when dependencies or runtime versions change. This harness should run quickly enough to execute on every change and should fail loudly when assumptions break.
Next, establish observability that captures both correctness and health. Structured logs should include correlation identifiers, key decision branches, and error classifications. Metrics should track throughput, latency percentiles, and error categories relevant to this workflow. If external integrations are involved, include dependency status and timeout counters so incident triage can isolate whether failures originate locally or downstream. Avoid relying on manual spot checks because intermittent regressions are often timing-sensitive and disappear outside repeatable test conditions.
Finally, define a controlled rollout and rollback process. Deploy incrementally, compare live metrics against baseline, and keep rollback criteria explicit before release starts. Store configuration assumptions in a short runbook so future maintainers can reproduce intended behavior quickly. A disciplined rollout model dramatically reduces recovery time when unexpected behavior appears after infrastructure, network, or platform changes.
Summary
Solving an arithmetic expression DAG is a topological-evaluation problem. Compute each node once in dependency order, detect invalid cycles early, and add validation and tracing for reliable real-world usage.

