DSA in Java: Dynamic Programming Made Simple (Beginner Guide)
Dynamic Programming demystified: it is just recursion that remembers. Learn memoization and tabulation in Java with Fibonacci, and how to approach any DP problem.
Dynamic Programming (DP) has a fearsome reputation — it's often the hardest topic in interviews. But here's a secret: at its core, DP is just "recursion that remembers its answers so it doesn't redo work." That's genuinely most of it. In this lesson we'll build DP up gently from an idea you already know, so it feels less like magic and more like common sense.
The problem DP solves: repeated work
Remember the recursive Fibonacci from the recursion lesson? It was elegant but slow — because it recomputes the same values over and over. Let's see exactly why:
fib(5) ├─ fib(4) │ ├─ fib(3) │ │ ├─ fib(2) ... │ │ └─ fib(1) │ └─ fib(2) ← computed AGAIN └─ fib(3) ← the WHOLE thing computed AGAIN └─ ...
fib(3) and fib(2) get calculated multiple times. For larger inputs this explodes into billions of repeated calls. DP's insight: compute each subproblem once, store the answer, and reuse it.
Technique 1: Memoization (top-down)
Memoization means "remember the result." You keep your natural recursion, but add a cache (a map or array): before computing, check if you've already solved this subproblem; after computing, store it. Watch how one small change transforms Fibonacci from exponential to O(n):
Map<Integer, Long> memo = new HashMap<>(); long fib(int n) { if (n <= 1) return n; if (memo.containsKey(n)) return memo.get(n); // already solved? reuse! long result = fib(n - 1) + fib(n - 2); memo.put(n, result); // store before returning return result; }
Those two extra lines are the whole trick. Now each fib(k) is computed just once. This "recursion + cache" approach is called top-down DP, because you start from the big problem and break it down.
Technique 2: Tabulation (bottom-up)
The other style, tabulation, flips it around: start from the smallest subproblems and build up to the answer using a table (usually an array), with a plain loop — no recursion:
long fib(int n) { if (n <= 1) return n; long[] dp = new long[n + 1]; dp[0] = 0; dp[1] = 1; // smallest answers (base cases) for (int i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; // build each from earlier answers } return dp[n]; }
Both are O(n). Memoization is often easier to write (just add a cache to your recursion); tabulation avoids recursion overhead. Learn both — you'll use whichever fits the problem.
When is a problem a DP problem?
Two signs tell you DP might apply:
- Overlapping subproblems — the same smaller problems get solved repeatedly (like
fib(3)above). This is what caching fixes. - Optimal substructure — the best answer to the big problem is built from the best answers to smaller pieces.
Classic DP problems you'll meet: climbing stairs (how many ways to reach step n?), the coin change problem, longest common subsequence, and the knapsack problem. They look different, but all share that "solve smaller pieces, combine, and don't repeat work" shape.
A practical way to approach any DP problem
- Solve it with plain recursion first — define the subproblem and base cases, ignoring efficiency.
- Spot the repeated work — are the same inputs computed more than once?
- Add a cache (memoize) — one map/array, check-before and store-after. Done — you have working DP.
- (Optional) Convert to bottom-up tabulation if you want to remove recursion.
This recipe — recursion first, then cache — is how most people actually write DP. You don't need to see the tabulated array immediately; grow into it.
I'll be honest: DP humbled me more than any other topic, and it's the one I still respect the most. What finally made it click wasn't a magic trick — it was reframing it as "recursion with a memory," and then grinding through a dozen problems until the patterns became familiar. My advice: don't try to master DP in a day. Learn memoized Fibonacci and climbing-stairs cold, then add one new DP problem to your practice each week. Slowly, the shapes repeat, and one day a "hard" DP problem will look reassuringly familiar. That day is worth the wait.
Key takeaways
- Dynamic Programming = recursion that remembers its subproblem answers to avoid repeated work.
- Memoization (top-down): keep recursion, add a cache — check before, store after.
- Tabulation (bottom-up): build answers from smallest to largest in a table with a loop.
- Look for overlapping subproblems + optimal substructure; approach any DP by writing recursion first, then caching.
← Previous: DSA Lesson 12 — Graphs (BFS & DFS)
Next: DSA Lesson 14 — The Interview Practice Plan →
↑ Back to the DSA roadmap