skip to content

Which Dijkstra invariant breaks when one link in a latency graph has a negative weight?

level: seniorimportance: must knowfreq 76%

answer

  1. ask what settling a vertex actually promises
  2. that promise depends on remaining edge signs
  3. a rebate later can undercut a finished answer
  4. wrong values feed the relaxations after them
  5. a constant charges per hop, not route

basics

~20 s

The finality invariant breaks: Dijkstra assumes an extracted vertex's distance can never improve, which holds only because remaining edges add non-negative amounts. One negative edge lets a cheaper route appear after a vertex is settled, and the wrong value propagates.

solid answer

~50 s

Dijkstra's correctness rests on one claim: when a vertex is extracted with tentative distance `d`, `d` is final. That claim is justified only by non-negativity - any alternative route must leave the settled set through a frontier vertex whose key is at least `d`, and non-negative edges can only add to it. A single negative link destroys the justification: a route can leave the settled region, pick up a rebate, and come back cheaper than a distance already declared final. Nothing detects it - the run terminates normally, reports distances that are silently too large, and because settled vertices feed later relaxations, one bad value contaminates everything settled after it. The seductive fix, adding a constant to every weight, is wrong: it charges each route the constant once per edge and so favours fewer hops. When negative weights are real, change the algorithm.

go deeper

for a junior

Remember the precondition itself: Dijkstra requires non-negative edge weights, and zero is fine. You are expected to state the rule even if you cannot yet justify it from the invariant.

for a middle

Explain the settled-vertex invariant and show precisely which step of its justification uses non-negativity. Be able to sketch a four-vertex graph where one negative edge yields a wrong distance.

for a senior

Defend the position under pushback: name the failure as silent and propagating, refuse the add-a-constant fix with the per-hop argument, and say how you would detect the problem in a running service.

for a principal

Own the systemic call: whether negative weights belong in the cost model at all, or whether the commercial rebate should be expressed as a separate objective, given what switching algorithms costs the routing service in latency and operability.

## The scenario A network-routing service picks paths across a provider mesh, with each link weighted by measured latency in milliseconds. A commercial team introduces a promotional peering link and models the rebate as a **negative** weight of -8, so that routes crossing it look cheaper. The existing shortest-path service keeps running, produces routes, and reports no errors. Someone asks whether the results are still trustworthy. They are not, and the reason is one specific invariant. ## The invariant Dijkstra maintains a settled set and a frontier. Its correctness claim is: > **When a vertex `u` is extracted with the minimum tentative distance `d`, `d` is the true shortest distance to `u`, and it will never change again.** The justification is entirely about the sign of the remaining weights. Any route to `u` other than the one already found must at some point cross from the settled region into the frontier, at some vertex `x` whose key is at least `d` (otherwise `x`, not `u`, would have been extracted). From `x` onward the route accumulates more weight, and if every weight is `>= 0`, the total can only grow. So no alternative route can beat `d`. Remove non-negativity and the last step collapses. From `x` onward the route can now accumulate **less** - the rebate link - so a route that looked worse at the frontier can arrive at `u` cheaper than `d`. Dijkstra has already declared `u` settled and moved on. ## Why the damage spreads A settled vertex is not merely a wrong number in a table; it is a **source of relaxations**. Every vertex settled after `u` may have had its distance computed from `u`'s inflated value. A single negative edge in a mesh of thousands of links can therefore produce a distance table where many entries are too large, and the errors are not localized near the negative link. This is what makes it dangerous rather than merely imprecise: you cannot look at the output and see which entries to distrust. ## What actually happens at runtime Three things that do **not** happen, all of which candidates guess: - It does not crash or raise an error. No implementation checks the sign. - It does not loop forever. Each vertex is settled once, so the loop still terminates. - It does not merely become slower. The bound is unchanged; the *answers* are wrong. The failure mode is a completed run with plausible-looking numbers. That is the worst kind. A variant worth knowing: if you drop the settled check and allow a settled vertex to be **reopened** when a smaller distance appears, correctness can be restored as long as there is no negative cycle - but you lose the performance guarantee entirely, and adversarial graphs drive the number of reopenings to exponential. "Just reopen nodes" is not a fix you can ship on an unbounded graph. ## The reweighting trap The most common wrong fix: *"add 8 to every link so nothing is negative, then run it as before."* This changes the problem. Adding a constant `C` to every edge charges a route `C` times its **hop count**, so a 2-hop route gains `2C` while a 5-hop route gains `5C`. Short-hop routes are systematically favoured, and the algorithm returns the shortest route under a different cost function than the one you asked about. On the latency mesh, a two-hop transcontinental link would start beating a five-hop chain of fast local links regardless of actual milliseconds. (There *is* a correct reweighting technique for negative-weight graphs, one that shifts each edge by a per-vertex potential rather than a global constant so that route costs shift uniformly - but it needs those potentials computed first by a method that tolerates negative weights, so it is not a way to avoid the problem.) ## What to say when a skeptic pushes back The skeptic's line is "shortest paths exist in this graph, so a shortest-path algorithm should find them." The answer is that existence of shortest paths is not the precondition Dijkstra needs. What it needs is that **extending a route never makes it cheaper** - that is the assumption baked into settling the frontier minimum. A graph can have perfectly well-defined shortest paths and still violate that assumption. When it does, reach for an algorithm designed for negative weights, such as Bellman-Ford, and accept its different cost profile; the details of that algorithm are a separate discussion. One honest boundary case: if the negative edges all leave the **source** and nothing else is negative, the greedy order happens to survive. Do not build a production argument on that - it is a fragile property that the next topology change deletes.

  • Why can't you just add a large constant to every weight to make them all non-negative?
    Because the shift is charged per edge, not per route. Adding `C` to every weight inflates a route by `C` times its hop count, so few-hop routes gain less than many-hop routes and the ranking changes. You would be solving a different problem - shortest under a hop-count-penalized cost - and returning its answer as if it were the original one.
  • Does the run fail loudly, or does it produce a wrong answer?
    It produces a wrong answer, quietly. No implementation checks edge signs, the loop still terminates because each vertex is settled once, and the runtime bound is unchanged. You get a complete distance table with some entries too large and no signal about which. Detection has to come from outside - validating input weights, or spot-checking against an algorithm that tolerates negative weights.
  • What if you allow already-settled vertices to be reopened when a smaller distance shows up?
    That restores correctness when there is no negative cycle, but it destroys the complexity guarantee. Each reopening re-relaxes a vertex's outgoing edges, and adversarial graphs can force exponentially many reopenings. It is a debugging expedient, not a production fix - if negative weights are genuinely part of the model, use an algorithm built for them.

saying these in an interview costs you the question

  • Says Dijkstra works wherever shortest paths exist
  • Proposes adding a constant to every weight
  • Claims the algorithm loops forever on negative edges
  • Expects an error or exception to be raised
  • Thinks only the negative edge's endpoints get wrong values

context