Senior 6 min · March 24, 2026

Gas Station Problem — Why Greedy Fails With Dynamic Costs

Trucks ran out of fuel despite valid start.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Lessons pulled from things that broke in production.

Follow
Production
production tested
May 23, 2026
last updated
1,596
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • The gas station problem finds the unique start station for a circular tour when total gas ≥ total cost.
  • Key invariant: if you run out at station K starting from S, any station between S and K also fails.
  • Algorithm runs in O(n) time with O(1) space — single pass with two counters.
  • Performance: worst-case still O(n) — no hidden quadratic behavior.
  • Production insight: in real fuel logistics, this greedy assumption holds only if costs are static and station order fixed.
  • Biggest mistake: trying all start stations naively O(n^2) instead of using the greedy reset.
✦ Definition~90s read
What is Gas Station Problem?

The Gas Station Problem is a classic algorithmic puzzle where you must determine if a circular route of gas stations can be completed given each station's fuel supply and the cost to travel to the next station. The twist: you start with an empty tank and can only refuel at stations.

Imagine petrol stations arranged in a circle.

The canonical version assumes static, known costs and fuel amounts, and a greedy O(n) solution works by tracking the net fuel surplus and resetting the start candidate when the running total goes negative. This works because the problem reduces to finding a point where the cumulative fuel never drops below zero — a property that holds due to the circular nature and the fact that total fuel must be at least total cost for any solution to exist.

However, the title's "dynamic costs" refers to scenarios where fuel prices or travel costs change over time or depend on the order of visits — a variant that breaks the greedy assumption. In the real world, gas prices fluctuate, and you might choose to buy less fuel at an expensive station and more at a cheap one, introducing a knapsack-like optimization.

The greedy algorithm fails here because it only considers immediate feasibility, not cost minimization. This mirrors problems in resource allocation, like scheduling with variable energy prices or routing with time-dependent tolls, where dynamic programming or more complex heuristics are required.

In practice, the static Gas Station Problem appears in coding interviews (LeetCode 134) and distributed systems (e.g., leader election in circular networks). The dynamic variant is closer to real logistics — companies like Uber Freight or Amazon's delivery network face this when fuel costs vary by location and time.

If you're solving the static version, the greedy solution is optimal and O(n). But if costs are dynamic, you need to model it as a shortest-path or DP problem, often with O(n^2) or worse complexity. Know which you're facing: the greedy trap is assuming all gas stations are equal when they're not.

Plain-English First

Imagine petrol stations arranged in a circle. Each station gives you some fuel, and it costs fuel to drive to the next station. You start empty. Which station should you start from to complete the full circle without running out? The greedy insight: if you run out at station k, don't start from any station between start and k — you would have run out even sooner starting from them.

The gas station problem is LeetCode 134 and a canonical example of a greedy algorithm with a non-obvious correctness proof. The O(n) solution looks almost too simple — one pass, tracking two sums — but the proof of why it works is the important part.

The key insight, once you see it, is unforgettable: if you start at station S and fail at station K, then no station between S and K can be a valid starting point. They would all fail at K too, having less fuel at that point than S did. This single observation collapses what looks like an O(n^2) search into a single linear pass.

Why the Gas Station Problem Is a Greedy Trap

The gas station problem asks: given a circular route with n stations, each providing a certain amount of fuel and requiring a certain cost to travel to the next station, can you complete the circuit starting from some station? The classic version assumes static costs — each station's fuel and travel cost are fixed. But when costs vary dynamically — based on time, traffic, or demand — the greedy algorithm that works for the static case breaks completely.

In the static version, the key insight is that if total fuel >= total cost, a valid start exists, and you can find it in O(n) by tracking cumulative surplus and resetting when it goes negative. This works because costs are independent of the order of traversal. With dynamic costs, the cost to travel from station i to i+1 may change depending on when you arrive, breaking the monotonicity that makes the greedy solution correct.

You reach for this problem when modeling real-world logistics: delivery fleets, electric vehicle charging networks, or any system where a resource must be consumed in sequence and replenished at stops. In production, dynamic costs are the norm — traffic, energy prices, or battery degradation all introduce time dependence. The static greedy solution is a useful baseline, but you must validate that your costs are truly static before relying on it.

Greedy Assumption Trap
The classic O(n) greedy solution assumes costs are fixed per edge. If costs depend on arrival time, the problem becomes NP-hard — don't apply the simple algorithm blindly.
Production Insight
Delivery route optimization with real-time traffic: the greedy algorithm picks a start station based on static fuel costs, but traffic jams cause fuel consumption to spike, and the vehicle runs out mid-route.
Symptom: the vehicle fails to complete the circuit even though total fuel >= total static cost — the dynamic cost edge exceeds the remaining fuel at that moment.
Rule of thumb: if edge costs can vary by more than 20% based on traversal order, treat the problem as dynamic and use DP or simulation, not the greedy O(n) solution.
Key Takeaway
The static gas station problem is solvable in O(n) with a greedy surplus check — but only if edge costs are invariant.
Dynamic costs turn the problem into a variant of the traveling salesman or shortest path with time windows — no simple linear solution exists.
Always profile cost variability in your data: if costs fluctuate with arrival time, the greedy algorithm will silently produce incorrect routes.
Gas Station Problem: Greedy vs Dynamic Costs THECODEFORGE.IO Gas Station Problem: Greedy vs Dynamic Costs Why the classic greedy fails when costs vary dynamically Greedy Trap Assumes static costs; fails with dynamic pricing O(n) Greedy Solution One-pass algorithm for fixed costs Why Greedy Works Total gas >= total cost ensures feasibility Worked Example Step-by-step demonstration of algorithm Edge Cases Uniqueness and degenerate inputs Proof of Correctness Mathematical justification for greedy ⚠ Greedy fails when costs change dynamically Use DP or graph algorithms for variable costs THECODEFORGE.IO
thecodeforge.io
Gas Station Problem: Greedy vs Dynamic Costs
Gas Station Problem

O(n) Greedy Solution

Key insight: if the total gas ≥ total cost, a solution always exists. To find it: track current tank. If tank goes negative, the current start is invalid — reset start to next station and reset tank.

gas_station.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def can_complete_circuit(gas: list[int], cost: list[int]) -> int:
    total_surplus = 0
    current_surplus = 0
    start = 0
    for i in range(len(gas)):
        diff = gas[i] - cost[i]
        total_surplus += diff
        current_surplus += diff
        if current_surplus < 0:
            # Can't start from any station 0..i
            start = i + 1
            current_surplus = 0
    return start if total_surplus >= 0 else -1

print(can_complete_circuit([1,2,3,4,5], [3,4,5,1,2]))  # 3
print(can_complete_circuit([2,3,4],     [3,4,3]))        # -1
Output
3
-1
Production Insight
In production, gas and cost arrays may be computed on the fly from GPS feeds.
Integer overflow in C++/Java can silently corrupt the surplus — always use long long or long.
Rule: test with station counts up to 10^6 and costs in billions to catch overflow early.
Key Takeaway
Single-pass greedy with surplus reset works because a negative surplus at any station proves the current start invalid.
O(n) time, O(1) space — no recursion or extra memory.
Always validate with edge cases (single station, zero surplus).

Why the Greedy Works

  1. If total gas ≥ total cost, a solution exists. The sum of all surpluses is non-negative, so there must be some starting point where cumulative sum never goes negative.
  2. If starting from S, you run out at station k, then no station between S and k works as a start. Proof: if you could start from some S' between S and k, then starting from S (which has non-negative surplus from S to S') would also work from S' — but we know you run out at k. Contradiction. So S' is also invalid.

This means once we reset start to i+1, all previous starts are definitively ruled out.

Key Invariant
At any point where we reset start to i+1, the current_surplus is negative. This means stations 0 through i have already collectively 'used up' whatever buffer existed — no subset starting point among them can succeed.
Production Insight
The proof assumes that fuel surplus is preserved exactly — no leakage, no variable consumption.
In real systems (e.g., electric delivery vans), consumption varies with load and terrain, breaking the invariant.
Rule: use greedy only when resource consumption is deterministic and additive.
Key Takeaway
If you fail at K, all stations between S and K also fail — proof by contradiction with prefix surplus.
Total surplus ≥ 0 is necessary and sufficient for a solution.
The greedy reset is not a heuristic — it's a proven reduction.

Worked Example

gas = [1, 2, 3, 4, 5] cost = [3, 4, 5, 1, 2] diff = [-2,-2,-2, 3, 3]

i=0: surplus=-2 < 0 → start=1, surplus=0 i=1: surplus=-2 < 0 → start=2, surplus=0 i=2: surplus=-2 < 0 → start=3, surplus=0 i=3: surplus=+3 → start still 3 i=4: surplus=+3+3=6 → start still 3 total_surplus = -2-2-2+3+3 = 0 ≥ 0 → return start=3 ✓

Verify: start at 3 with 0 fuel. - Station 3: +4, -1 = 3 remaining. Drive to 4. - Station 4: +5, -2 = 6. Drive to 0. - Station 0: +1, -3 = 4. Drive to 1. - Station 1: +2, -4 = 2. Drive to 2. - Station 2: +3, -5 = 0. Back to start. ✓

Production Insight
Manual trace catches off-by-one errors that automated tests miss.
When validating, always simulate the full tour — don't just trust the algorithm's return value.
Rule: for every production deployment, include invariant assertions that the returned start actually works.
Key Takeaway
The algorithm resets three times before finding station 3.
Manual verification confirms the tour completes with zero final surplus.
Trace by hand until the pattern becomes automatic — it reveals subtle logic bugs.

Edge Cases and Uniqueness

When total surplus ≥ 0, exactly one starting station is valid. Proof: Suppose two valid starts, A and B. Starting from A, you reach B with non-negative surplus. From B, you can complete the circuit, so the total surplus from A to B plus B's circuit is … This leads to a contradiction because the sum of surpluses around the circle would be positive at two disjoint points.

Edge cases to test
  • Single station: gas=[5], cost=[3] → start=0. gas=[3], cost=[5] → -1.
  • All stations have zero surplus: any start works (uniqueness broken only when total surplus exactly 0 at every prefix? Actually if all diffs are 0, every station works uniquely? No, if all zero, then starting at any station, you have 0 fuel at each step, so you complete. Multiple starts exist. But total surplus = 0. The uniqueness proof assumes that the cumulative surplus never goes negative; if it's always exactly zero, multiple starts can work. However that's a degenerate case. In production, handle by returning the first valid start (0) or any, per problem constraints.
  • Large positive surplus only at last station: e.g., gas=[0,0,10], cost=[1,1,1] → surplus = [-1,-1,9], total=7. Start should be 2.
Production Insight
Degenerate cases (all zero surplus) are rare but must not crash the system.
Uniqueness fails when the cumulative surplus never drops below zero for multiple prefixes — the greedy still finds one, but others exist.
Rule: if exact start uniqueness is required, add a post-check that other starts fail.
Key Takeaway
Uniqueness holds except when all prefix surpluses are ≥ 0 from multiple starting points.
Test with zero-surplus arrays to confirm your algorithm handles them gracefully.
Uniqueness is not guaranteed in degenerate cases — document this assumption.

Proof of Correctness (Detailed)

Lemma: If a circuit is possible (total surplus ≥ 0), the greedy algorithm returns a valid start.

Proof by induction on the number of resets. Base case: no reset → start=0 works because current_surplus never negative. Inductive step: Assume algorithm resets at station k. Then stations 0..k are ruled out. The problem reduces to finding start in subarray k+1..n-1 with total surplus unchanged (since total_surplus already accumulated from 0..k). By invariant, the remaining stations still have total surplus ≥ 0, so by induction a solution exists.

Thus O(n) correctness holds. The critical insight: a negative surplus at k means the cumulative sum from start to k is negative, so any earlier start would have even lower sum at k, confirming no valid start lies within.

Mental Model: Prefix Sum as a Hopping Point
  • Each station adds or subtracts from your balance.
  • If the balance goes negative, you fall — that's your current start invalidated.
  • Resetting is like jumping to a new starting point after the last fall.
  • The total surplus is the net change after walking the entire circle.
Production Insight
This proof is also the reason the algorithm can't be easily parallelized — each step depends on cumulative state.
For distributed systems, you'd need to precompute prefix sums in parallel, which defeats the O(1) space advantage.
Rule: know your algorithm's sensitivity to sequential dependency; don't over-optimize.
Key Takeaway
Proof by induction: reset reduces problem size while preserving total surplus.
The greedy works because each reset eliminates a contiguous block of potential starts.
Never over-engineer — the simple loop with two variables is correct and optimal.

The Naive Approach: Burning the Planet for a Test Drive

Most devs hit this problem and think: "Just try every station." That's the O(n²) solution — traverse the circle from each index until you either complete it or run dry. It works, you'll pass the interview, and your code will be slower than a memory leak on a Friday deploy.

Why is this bad? Because the problem is telling you something important: solution is unique. That means you're wasting time simulating loops that have zero chance of success. The naive approach checks each station independently, resetting the tank to zero like a rookie who doesn't read the logs.

You simulate a full circuit from station i. If you hit negative fuel at any point, you break and move to i+1. Circular indexing is handled with modulo. The tank starts at 0. If you survive n steps without going negative, you found your station. Otherwise, return -1.

This is fine for small inputs but will choke on the 10⁵ station test cases your platform will throw at you. The O(n²) cost is a bill that compounds. Let's look at the code so you can laugh at it before we move to the real solution.

GasStationNaive.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// io.thecodeforge — dsa tutorial

public class GasStationNaive {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int n = gas.length;
        
        for (int start = 0; start < n; start++) {
            int tank = 0;
            boolean possible = true;
            
            for (int steps = 0; steps < n; steps++) {
                int idx = (start + steps) % n;
                tank += gas[idx] - cost[idx];
                if (tank < 0) {
                    possible = false;
                    break;
                }
            }
            
            if (possible) return start;
        }
        
        return -1;
    }
}
Output
For gas = [4,5,7,4], cost = [6,6,3,5]:
Starts tried: 0 (fails), 1 (fails), 2 (succeeds) -> returns 2
Production Trap: Premature Reset
Resetting tank = 0 at each start kills your ability to reuse any computation. The greedy solution abuses the fact that you only need one pass — the naive approach throws away perfectly good intermediate state.
Key Takeaway
Never brute-force a circular problem when you can exploit the uniqueness constraint — O(n²) is for prototypes, not production.

The One-Pass Greedy: How to Skip Half the Work Without Guilt

Here's the trick that separates senior engineers from script kiddies: if you start at station 0 and run out of gas at station k, you can't start anywhere between 0 and k and succeed. Why? Because your tank was accumulating gas from every station before k. If you started later, you'd have even less gas at the bottleneck.

This is the insight that collapses O(n²) to O(n). You track total gas and total cost across the whole route. If total gas < total cost, return -1 immediately — you're never making it around the circle. If it's possible, you just need one pass to find where to start.

You keep a running tank balance and a candidate start index. Each time your tank goes negative, you reset the start to i+1 and your tank to 0. The algorithm feels like cheating. It is cheating. But it's mathematically sound and runs in one pass with constant space.

The key is trust. You don't re-check stations you've already proven can't work. That's the greedy mindset: make the locally optimal decision (reset here), and the global optimum falls out. This pattern appears in stock trading, interval scheduling, and a dozen other interview traps.

GasStationOnePass.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge — dsa tutorial

public class GasStationOnePass {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int totalGas = 0, totalCost = 0;
        int tank = 0, startIdx = 0;
        
        for (int i = 0; i < gas.length; i++) {
            totalGas += gas[i];
            totalCost += cost[i];
            tank += gas[i] - cost[i];
            
            if (tank < 0) {
                startIdx = i + 1;
                tank = 0;
            }
        }
        
        return totalGas >= totalCost ? startIdx : -1;
    }
}
Output
Input: gas = [4,5,7,4], cost = [6,6,3,5]
Output: 2
Input: gas = [3,9], cost = [7,6]
Output: -1
Senior Shortcut: The Accumulation Principle
Whenever you see "circular" + "unique solution", your first question should be: "Can I prove that if I fail at k, all earlier starts fail too?" If yes, one-pass greedy is your weapon. No other data structures needed.
Key Takeaway
Track total viability first (O(1) early exit), then use negative tank as a reset trigger — the greedy leap of faith that crushes O(n²) complexity.
● Production incidentPOST-MORTEMseverity: high

Fuel Delivery Scheduling: Greedy Algorithm Failure Under Dynamic Costs

Symptom
The system returned a valid starting station, but trucks consistently ran out of fuel before completing the route during peak traffic hours.
Assumption
The team assumed the greedy invariant (total gas ≥ total cost guarantees a start) holds for any cost distribution, including time-dependent costs.
Root cause
The greedy algorithm works only when costs are static and known in advance. Time-of-day pricing introduced dynamic costs that broke the invariant — the 'total cost' used at planning time differed from actual cost during execution.
Fix
Switch to a two-phase approach: first compute a static schedule using the greedy algorithm for initial planning, then add a real-time cost re-evaluation every 30 minutes to rebalance the start point if the invariant is violated.
Key lesson
  • Greedy algorithms assume monotonic stability — any dynamic input breaks the proof.
  • Always validate that the algorithm's invariants hold under production conditions.
  • For time-varying data, use receding horizon or rolling horizon planning.
  • When in doubt, add a runtime invariant check before committing to a route.
Production debug guideCommon symptoms and actions when the greedy algorithm returns -1 or a wrong start4 entries
Symptom · 01
Algorithm returns -1 but total surplus ≥ 0
Fix
Check for integer overflow in surplus accumulation (use long in Java). Verify gas and cost arrays have same length. Test with edge cases: single station, all zeros.
Symptom · 02
Algorithm returns start station that cannot complete the circuit
Fix
Manually simulate the tour using a loop. Likely cause: the algorithm reset logic is incorrect or the total surplus check was skipped.
Symptom · 03
Algorithm O(n^2) brute-force passes but greedy fails
Fix
Brush up on the invariant proof: if you fail at K, reset start to K+1. Ensure the reset happens immediately on negative current_surplus, not deferred.
Symptom · 04
Performance is slow for large n (n > 10^5)
Fix
Your implementation may have hidden O(n^2) behavior due to nested loops or recomputed sums. Verify single-pass structure with two counters only.
★ Gas Station Algorithm Quick Fixes3 common coding mistakes and their immediate fixes
Reset start to i instead of i+1
Immediate action
Change start = i to start = i + 1. The current station is the one that caused negative surplus — it cannot be a valid start.
Commands
Check debug output: print current_surplus after each iteration.
Simulate manually with small input.
Fix now
Update reset line: start = i + 1; current_surplus = 0;
Missing total surplus check before final return+
Immediate action
Add 'if total_surplus < 0: return -1' before returning start.
Commands
Log total_surplus after loop.
Test with case where total_surplus < 0 (e.g., gas=[1], cost=[2]).
Fix now
Add conditional return at end.
Using integer for diff where gas[i] - cost[i] may overflow+
Immediate action
Change diff to long in Java/c++ or ensure Python uses int (unbounded).
Commands
Print min and max values of gas and cost.
Test with large numbers (e.g., 10^9).
Fix now
Declare diff as long or use guard condition: if (gas[i] - cost[i] > 0) ...
Algorithm Complexity Comparison
ApproachTimeSpaceWhen to Use
Greedy (this)O(n)O(1)Always fastest, but requires static costs
Brute Force (simulate each start)O(n²)O(1)When n is small (< 1000) and code simplicity matters
DP with prefix sumsO(n)O(n)If you need to answer multiple queries for different start sets

Key takeaways

1
If total gas ≥ total cost, exactly one valid starting station exists.
2
Track cumulative surplus; when it goes negative, reset start to next station.
3
O(n) time, O(1) space
single pass.
4
The greedy works because a failed start rules out all intermediate stations as potential starts.
5
Total surplus ≥ 0 is both necessary and sufficient for a solution to exist.

Common mistakes to avoid

3 patterns
×

Resetting start to i instead of i+1

Symptom
The algorithm returns the station that caused the negative surplus as the start, leading to infinite loop or wrong answer.
Fix
Change 'start = i' to 'start = i + 1'. The station that caused negative surplus cannot be a valid start.
×

Skipping the total surplus check and always returning start

Symptom
The algorithm returns a start even when no tour exists (e.g., gas=[1], cost=[2] returns 0 or 1 incorrectly).
Fix
Add 'if total_surplus < 0: return -1' before the return statement.
×

Using 'start = i + 1' but not resetting current_surplus to 0

Symptom
After reset, the algorithm accumulates previous negative surplus into the new start's calculation, causing false negatives.
Fix
After resetting start, always set current_surplus = 0. The two operations must be paired.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Why does resetting start to i+1 when surplus goes negative always give t...
Q02SENIOR
Prove that if total gas ≥ total cost, a valid starting station always ex...
Q03JUNIOR
What is the time and space complexity of the greedy gas station solution...
Q04SENIOR
Can there be more than one valid starting station? Why or why not?
Q05SENIOR
How would you modify the algorithm to return all valid starting stations...
Q01 of 05SENIOR

Why does resetting start to i+1 when surplus goes negative always give the correct answer?

ANSWER
Because if you start at S and fail at K, any station between S and K would have even less fuel when reaching K (since the prefix from S to that station is non-negative, your surplus at that intermediate station is at least the surplus S had there). So they would also fail at K. Resetting to K+1 is safe: you've ruled out all stations up to K.
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
Is there a case with exactly one valid start vs multiple valid starts?
02
What happens if the total surplus is exactly zero?
03
Can this algorithm handle negative costs?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Lessons pulled from things that broke in production.

Follow
Verified
production tested
May 23, 2026
last updated
1,596
articles · all by Naren
🔥

That's Greedy & Backtracking. Mark it forged?

6 min read · try the examples if you haven't

Previous
Interval Scheduling and Meeting Rooms Problem
13 / 13 · Greedy & Backtracking
Next
Divide and Conquer — Strategy and Patterns