Senior 4 min · March 05, 2026

Rod Cutting Problem — O(2^n) Crashes After n=35

Recursive rod cutting crashed pricing engine at length 40 with StackOverflowError, 30-second delays from length 20.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.

Follow
Production
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Rod cutting: maximize revenue by cutting a rod of length n into pieces sold at given prices per length
  • dp[i] = maximum revenue for rod length i; recurrence: dp[i] = max(dp[i], price[j] + dp[i-j]) for j=1..i
  • Optimal substructure: the best cut at length j leaves a sub-problem of length i-j that must also be optimal
  • Time O(n²), Space O(n) — can be reduced to O(n) space with bottom-up tabulation
  • Production gotcha: Recursive without memoization hits O(2^n) — stack overflows at n≈40; always use DP
✦ Definition~90s read
What is Rod Cutting Problem?

The Rod Cutting problem: given a rod of length n and a price table where price[i] is the value of a piece of length i, determine the maximum revenue obtainable by cutting the rod and selling the pieces. Pieces can be sold in any combination. You choose how many cuts to make and where.

Imagine you have a chocolate bar with 10 squares and a price list: 1 square sells for $1, 2 squares sell for $5, 3 squares sell for $8, and so on.

Example: a rod of length 4 with prices [0, 2, 5, 7, 8] (price[1]=2, price[2]=5, price[3]=7, price[4]=8). Selling as two pieces of length 2 yields 5+5=10, better than the unsplit price of 8.

Plain-English First

Imagine you have a chocolate bar with 10 squares and a price list: 1 square sells for $1, 2 squares sell for $5, 3 squares sell for $8, and so on. You want to break the bar and sell the pieces to make the most money possible. The rod cutting problem is exactly that — you have a rod of length N, a price for every possible piece length, and your job is to figure out the most profitable way to cut it up (or not cut it at all).

Every compiler that packs instructions into cache lines, every stock trader slicing a large order into smaller trades, every furniture manufacturer deciding how to cut timber to minimize waste — they're all solving variations of the same fundamental optimization question: given a resource you can divide, what's the most valuable partition? The rod cutting problem is the canonical formulation of that question, and it sits at the intersection of combinatorics and optimization in a way that makes it a perfect vehicle for understanding dynamic programming at depth.

What is Rod Cutting? — Plain English

The Rod Cutting problem: given a rod of length n and a price table where price[i] is the value of a piece of length i, determine the maximum revenue obtainable by cutting the rod and selling the pieces. Pieces can be sold in any combination. You choose how many cuts to make and where.

Example: a rod of length 4 with prices [0, 2, 5, 7, 8] (price[1]=2, price[2]=5, price[3]=7, price[4]=8). Selling as two pieces of length 2 yields 5+5=10, better than the unsplit price of 8.

Production Insight
A common production mistake is to confuse price indexing: price[1] is the first piece, not price[0].
Always align your price array with length: price[0] is a dummy value (usually 0).
If your price list starts at index 0 for length 1, the recurrence becomes price[j-1] + dp[i-j] — a frequent off-by-one source.
Key Takeaway
The rod length units must map directly to price indices.
price[i] is the revenue for a piece of length i, not for the i-th cut.
Get the index mapping right or your DP fails silently.
Rod Cutting: Recursion vs Memoization THECODEFORGE.IO Rod Cutting: Recursion vs Memoization From exponential brute force to O(n²) DP solution Rod Cutting Problem Given length n and price array, maximize revenue Recursive Baseline O(2^n) — crashes for n > 35 Top-Down Memoization Cache subproblem results, O(n²) time Reconstruct Cuts Track first cut to output piece lengths Optimal Revenue Maximum profit from rod of length n ⚠ Recursive O(2^n) is unusable beyond n=35 Always use DP memoization for production THECODEFORGE.IO
thecodeforge.io
Rod Cutting: Recursion vs Memoization
Rod Cutting Problem

How Rod Cutting Works — Step by Step

Define dp[i] = maximum revenue from a rod of length i.

  1. dp[0] = 0 (zero-length rod has zero value).
  2. For each length i from 1 to n:
  3. For each first cut at length j from 1 to i:
  4. dp[i] = max(dp[i], price[j] + dp[i - j])
  5. (price[j] is the value of selling a piece of length j; dp[i-j] is the optimal value of the remainder)
  6. Return dp[n].

Time: O(n^2). Space: O(n).

Production Insight
The O(n^2) time is fine for n ≤ 10,000, but for n = 10^6 you need a different approach (e.g., monotonic queue or convex hull trick if price function is concave).
In production, always wrap DP in a input-size check to avoid silent O(n^2) explosion.
Measure your actual max N in the business domain — don't assume it's small.
Key Takeaway
dp[i] builds on smaller subproblems — the optimal substructure is key.
The inner loop tries every possible first cut, which is brute force over combinatorial choices.
Memorize the recurrence pattern: it's the same for unbounded knapsack and coin change.

Worked Example — Tracing the Algorithm

Rod length n=4. Prices: price[1]=2, price[2]=5, price[3]=7, price[4]=8.

dp[0]=0.

dp[1]: try j=1: price[1]+dp[0]=2+0=2. dp[1]=2.

dp[2]: try j=1: price[1]+dp[1]=2+2=4. try j=2: price[2]+dp[0]=5+0=5. dp[2]=max(4,5)=5.

dp[3]: try j=1: price[1]+dp[2]=2+5=7. try j=2: price[2]+dp[1]=5+2=7. try j=3: price[3]+dp[0]=7+0=7. dp[3]=7.

dp[4]: try j=1: price[1]+dp[3]=2+7=9. try j=2: price[2]+dp[2]=5+5=10. try j=3: price[3]+dp[1]=7+2=9. try j=4: price[4]+dp[0]=8+0=8. dp[4]=max(9,10,9,8)=10.

Maximum revenue for length-4 rod = 10 (cut into two pieces of length 2).

Production Insight
Tracing by hand catches index errors that automated tests miss. For example, if price[2] was accidentally 6, dp[2] would become max(4,6)=6 and dp[4] would become max(9,10,9,8)=10 still, but the optimal cut changes.
In production, verify the trace for small cases before deploying to larger inputs.
Key Takeaway
dp[i] only depends on dp values for lengths < i — this is why bottom-up works.
The max comes from comparing all possible first cuts; there's no greedy shortcut.
Tracing teaches you the recurrence until it becomes second nature.

Implementation — Java

The following Java implementation demonstrates rod cutting with both top-down memoization and bottom-up tabulation. The code is ready for production use with proper edge case handling.

io/thecodeforge/RodCutting.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
26
27
28
29
30
package io.thecodeforge;

public class RodCutting {
    public static int bottomUp(int[] prices, int n) {
        int[] dp = new int[n + 1];
        dp[0] = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                dp[i] = Math.max(dp[i], prices[j] + dp[i - j]);
            }
        }
        return dp[n];
    }

    public static int topDown(int[] prices, int n) {
        Integer[] memo = new Integer[n + 1];
        return helper(prices, n, memo);
    }

    private static int helper(int[] prices, int n, Integer[] memo) {
        if (n == 0) return 0;
        if (memo[n] != null) return memo[n];
        int max = 0;
        for (int j = 1; j <= n; j++) {
            max = Math.max(max, prices[j] + helper(prices, n - j, memo));
        }
        memo[n] = max;
        return max;
    }
}
Output
// For prices = {0, 2, 5, 7, 8} and n=4:
// bottomUp(prices,4) → 10
// topDown(prices,4) → 10
Production Insight
The top-down version may still cause stack overflow for n > 10,000 because each recursive call adds a frame. Switch to bottom-up for large n. Also note the Integer[] memo initializes with null — using int[] with sentinel -1 is faster and avoids boxing overhead.
In high-throughput systems, pre-allocate dp arrays once and reuse them for repeated queries of varying lengths.
Key Takeaway
Bottom-up is safer for large inputs: no recursion, no stack overflow.
Top-down with memoization is more intuitive for deriving the recurrence.
Always prefer bottom-up when the input size is unbounded.

Reconstructing the Cuts — Which Pieces to Sell

The DP only tells you the maximum revenue, not which cuts yield it. To know the actual cutting plan, maintain a 'firstCut' array where firstCut[i] stores the length of the first piece in the optimal solution for rod length i. After computing dp, trace back from n to 0.

Algorithm: 1. Initialize firstCut[0] = 0. 2. In the inner loop, when dp[i] is updated with price[j] + dp[i-j], record firstCut[i] = j. 3. To reconstruct: start at i = n, while i > 0: print firstCut[i]; i -= firstCut[i].

For the example: firstCut[1]=1, firstCut[2]=2, firstCut[3]=1 (or 3, tie-breaking can go to any), firstCut[4]=2. Tracing: i=4 → cut length 2, i=2 → cut length 2, i=0 stop. Pieces: [2,2].

Production Insight
If multiple cut combinations yield the same revenue, the reconstruction picks one based on tie-breaking in the max check — usually the first j encountered. This can be non-deterministic if you use >= vs >.
In business contexts (e.g., lumber pricing), you might want to prefer fewer cuts or specific lengths. Add a secondary objective by adjusting the comparison accordingly.
Key Takeaway
Store the first cut that gives the optimal value for each subproblem.
Trace back using subtraction to recover the full solution.
Tie-breaking matters when multiple optimal solutions exist — document your policy.

Variations and Production Considerations

Rod cutting is a direct analog of the unbounded knapsack problem: each piece length is an 'item' with weight = length and value = price, and you can take unlimited copies (since multiple pieces of the same length are allowed).

Variations you'll encounter
  • Cutting costs: subtract a fixed cost per cut (except when no cut is made).
  • Length limits: some piece lengths may be unavailable (set price[j] = -inf).
  • Minimum piece length: only sell pieces of at least L (modify inner loop range).
  • Multiple rods: maximize total revenue from a set of rods (2D DP).

Production pattern: When the price list follows a known function (e.g., linear with discounts for longer pieces), the DP can be replaced with a greedy algorithm or a formula — test for this before using DP blindly.

Production Insight
Always measure the actual price distribution. If prices are nearly proportional to length, the optimal is often to sell the whole rod (no cuts). DP is overkill but safe. If prices are arbitrary, DP is necessary.
Cutting costs change the recurrence: dp[i] = max over j of price[j] + dp[i-j] - (cutCost if i-j != 0 else 0). Watch for the off-by-one on cut cost.
Key Takeaway
Rod cutting is the prototype for unbounded knapsack — master it and you master a family of problems.
Real-world constraints (cost per cut, minimum length) modify the recurrence slightly but keep the same DP skeleton.
Always profile before optimizing: if n < 100, O(n²) is fine.

Using Recursion – The Baseline You Shouldn't Ship

Before we get to the good stuff, let's look at the raw recursive approach. It's the naive solution interviewers expect you to mention before you show them something better.

The idea is brutally simple: for a rod of length i, try every possible first cut j from 1 to i. The revenue is price[j] plus whatever you can get from the remaining length i - j. Take the max. That's it.

The recursion tree explodes. For each length i, you branch into i possibilities. That gives you O(2^n) time. For n=10 you're fine. For n=30 you're waiting minutes. For n=50 the heat death of the universe becomes a concern.

Why waste time on this? Because it's the clearest expression of the problem's structure. Every DP solution is just this recursion with caching or iteration — same logic, no recomputation. Understand this first, then optimise.

RodCutRecursive.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 RodCutRecursive {
    public static int cutRod(int[] price, int n) {
        // base case: rod of length 0 yields nothing
        if (n == 0) return 0;
        int best = 0;
        // try every possible first cut
        for (int cut = 1; cut <= n; cut++) {
            int revenue = price[cut] + cutRod(price, n - cut);
            if (revenue > best) best = revenue;
        }
        return best;
    }

    public static void main(String[] args) {
        int[] price = {0, 1, 5, 8, 9, 10, 17, 17, 20};
        int rodLen = price.length - 1;
        System.out.println(cutRod(price, rodLen));
    }
}
Output
22
Production Trap:
Never use this in production. O(2^n) is not 'just slow' — it's non-terminating for any real input. Interviewers will grill you on why it's garbage and how to fix it.
Key Takeaway
Recursion makes the problem structure obvious at the cost of exponential time. Use it only to reason, never to run.

Top-Down DP (Memoization) – The Pragmatic Fix

Same recursion, one tweak: cache results. That's it. The only difference from the naive version is you check if you've already computed cutRod(i) before recursing. If yes, return the cached value. No recomputation, no branches you've already explored.

This drops complexity from O(2^n) to O(n^2). Why n^2? For each of n lengths, you consider n possible cuts. The memoisation ensures each subproblem is solved exactly once.

Space is O(n) for the cache array. Plus O(n) for the call stack if you recurse deep — which hits stack overflow for n > ~10k in Java. That's why bottom-up is safer, but for interview or prototyping clarity, top-down is king.

The pattern: if you can write the recursion, you can memoise it. No new theory. Just add a lookup table and a guard clause. Every production DP I've written started as memoised recursion before getting refactored to tabulation for performance.

RodCutMemo.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

import java.util.Arrays;

public class RodCutMemo {
    public static int cutRod(int[] price, int n, int[] memo) {
        if (n == 0) return 0;
        if (memo[n] != -1) return memo[n];  // cached
        int best = 0;
        for (int cut = 1; cut <= n; cut++) {
            int revenue = price[cut] + cutRod(price, n - cut, memo);
            if (revenue > best) best = revenue;
        }
        memo[n] = best;
        return best;
    }

    public static void main(String[] args) {
        int[] price = {0, 1, 5, 8, 9, 10, 17, 17, 20};
        int n = price.length - 1;
        int[] memo = new int[n + 1];
        Arrays.fill(memo, -1);
        System.out.println(cutRod(price, n, memo));
    }
}
Output
22
Senior Shortcut:
When mapping recursion to memo, always initialise the cache with a sentinel value (like -1) that can't be a valid result. Saves you from debugging with 0 which might actually be a valid answer.
Key Takeaway
Memoisation is the cheapest optimisation you can make. If you can write recursion, adding a cache is a 5-minute refactor with O(n^2) payoff.
● Production incidentPOST-MORTEMseverity: high

Recursive Rod Cutting Crashes Production Pricing Engine

Symptom
Pricing API returned HTTP 500 for rod lengths over 35. For lengths 20-35, responses took >30 seconds. For length 40, the JVM crashed with StackOverflowError.
Assumption
Recursive code would be fine because the maximum order length was thought to be 25. No one tested with length 40.
Root cause
The recursive solution without memoization has exponential time O(2^n). At n=40, that's over a trillion recursive calls. The JVM default stack size couldn't handle the call depth either — the recurrence depth equals n, so a stack depth of 40 frames is fine, but the branching factor caused massive repetition. The real culprit was the exponential number of subproblem recomputations leading to thread starvation and eventual timeout before the stack overflow.
Fix
Replaced recursive approach with bottom-up DP (O(n²) time, O(n) space). Deployed with health checks verifying max length 100 completes under 50ms.
Key lesson
  • Always benchmark your DP solution at the maximum input size the system will see. Exponential algorithms are invisible in small tests.
  • Never assume 'small input' stays small. A pricing engine that starts with length 25 can silently be asked for length 100 when a new customer orders larger rods.
  • Use explicit DP (iterative tabulation) for any problem where the maximum N is known and > 20. Memoization has better average-case but same worst-case stack depth.
Production debug guideSymptom-to-action guide for common rod cutting DP failures in production and development4 entries
Symptom · 01
DP table values are all zeros or very small
Fix
Check your base case: dp[0] must be 0. Then verify that the price array is 1-indexed and prices[0] is unused (or set to 0). In the loop, ensure you start j from 1, not 0.
Symptom · 02
Result is correct for small N but wrong for larger N
Fix
Likely an off-by-one in array indexing. Rod length n means dp array size n+1. The recurrence should consider all first-cut lengths j from 1 to i. Double-check the loop boundary: for j=1; j<=i; j++.
Symptom · 03
Memoized solution throws StackOverflowError for N > 1000
Fix
Switch to bottom-up tabulation. Memoization uses recursion depth equal to N, which can overflow the call stack. Bottom-up eliminates recursion entirely.
Symptom · 04
Result is higher than the theoretical maximum (e.g., selling the whole rod gives 8, but DP returns 12 for length 4 with given prices)
Fix
You are likely summing prices[j] + dp[i-j] where j exceeds the rod length because the inner loop goes to n instead of i. The inner loop must iterate only to the current length i.
★ Quick Debug: Rod Cutting DPCommands and checks to run when your rod cutting DP returns wrong values or crashes
dp[4] returns 8 but expected 10 (for example prices)
Immediate action
Print dp array after each iteration. Check if dp[2] equals max(price[1]+dp[1], price[2]+dp[0]) = max(2+2,5+0)=5.
Commands
System.out.println(Arrays.toString(dp)); // after each i
Add assertion: dp[2] == 5 before computing dp[3]
Fix now
Ensure price array is 1-indexed and price[0] is 0.
StackOverflowError for N=50+
Immediate action
Check if you are using recursion without memoization. Count function calls with a static counter.
Commands
Add a cache: int[] memo = new int[n+1]; Arrays.fill(memo, -1);
Convert to bottom-up as a permanent fix.
Fix now
Replace recursive call with iterative DP loop.
Result decreases when you add a higher price for a shorter piece+
Immediate action
Look for a sign error in the recurrence. You might be subtracting instead of adding.
Commands
Print the candidate value: price[j] + dp[i-j] for each j
Verify that price[j] is the value for length j, not for remaining length.
Fix now
Correct recurrence: dp[i] = max(dp[i], price[j] + dp[i-j]).
Rod Cutting vs Related DP Problems
ConceptUse CaseExample
Rod Cutting ProblemCore usageSee code above
Unbounded KnapsackItem can be used multiple timesrod cutting with weight = length, value = price
0/1 KnapsackEach item used at most onceinner loop j goes from 1 to n, but dp[i][w] 2D table
Coin Change (min coins)Minimize number of coins to reach sumdp[i] = min(dp[i], 1 + dp[i - coin])
Rod Cutting with Cut CostEach cut reduces total revenuedp[i] = max(price[i], max_{j=1..i-1} price[j] + dp[i-j] - C)

Key takeaways

1
Rod cutting finds the maximum revenue by choosing how to cut a rod into pieces.
2
dp[i] = max revenue for rod of length i. Recurrence
max over all first-cut lengths j.
3
This is equivalent to unbounded knapsack where cut lengths are reusable items.
4
Time
O(n^2). Space: O(n).
5
Track a 'cuts' array to reconstruct which cuts produce the optimal revenue.
6
Production rule
always benchmark with max expected N; naive recursion is dangerous.

Common mistakes to avoid

4 patterns
×

Memorising syntax before understanding the concept

Symptom
Unable to derive the recurrence relation during interviews; follows template but can't handle variations like cut costs or length limits.
Fix
Focus on the intuition: dp[i] = maximum over all first-cut lengths j of (revenue from selling that piece + optimal revenue from remaining length). Draw the recursion tree for n=4.
×

Skipping practice and only reading theory

Symptom
Stuck when the problem statement changes slightly (e.g., 'sell pieces in a specific order' or 'cutting costs apply').
Fix
Implement rod cutting from scratch in a language you know well. Then modify: add cut cost, change to allow only certain piece lengths, then implement reconstruction. Write at least three variations.
×

Using recursion without memoization

Symptom
StackOverflowError for n as low as 40, or exponential runtime making the program unusable.
Fix
Always add memoization or use bottom-up DP. The naive recursive solution has O(2^n) time. Even with memoization, the stack depth is O(n) which can overflow for n > 10^4.
×

Off-by-one in price array indexing

Symptom
Results are incorrect: for n=4, returns 9 instead of 10 because price[2] is read as index 2 but the real value is at index 3, or price[0] is used for length 1.
Fix
Ensure price array is 1-indexed where price[i] corresponds to length i piece. If input is 0-indexed (price[0]=2 for length 1), use price[j-1] in the recurrence.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the recurrence relation for the rod cutting DP?
Q02SENIOR
How is rod cutting similar to the unbounded knapsack problem?
Q03SENIOR
How would you reconstruct the actual cuts from the DP table?
Q04JUNIOR
What are the time and space complexities of the bottom-up DP solution?
Q05SENIOR
How would you modify the rod cutting recurrence if each cut costs C doll...
Q01 of 05SENIOR

What is the recurrence relation for the rod cutting DP?

ANSWER
Let price[i] be the value of a piece of length i, and dp[i] be the maximum revenue from a rod of length i. The recurrence is: dp[i] = max_{1 ≤ j ≤ i} (price[j] + dp[i-j]) with base case dp[0] = 0. This considers cutting off a piece of length j and then optimally solving the remaining length i-j.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
How is rod cutting related to the unbounded knapsack problem?
02
How do you find the actual cuts, not just the maximum value?
03
What if cut costs are involved?
04
Can rod cutting be solved with a greedy algorithm?
05
What is the space-optimized version of the DP?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.

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

That's Dynamic Programming. Mark it forged?

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

Previous
Edit Distance Problem
9 / 15 · Dynamic Programming
Next
Fibonacci with DP