Skip to content
← Back to Blog

Dynamic Programming for Beginners: The Complete Introduction

12 min read

Dynamic programming (DP) is one of the most feared topics in coding interviews — and one of the most rewarding to learn. Once you see the underlying pattern, problems that looked impossible become straightforward. This guide takes you from zero to confident with DP, covering the two core principles, the difference between top-down and bottom-up approaches, all six major DP sub-patterns, and fully worked examples you can follow line by line.

What Is Dynamic Programming?

Dynamic programming is a problem-solving technique that breaks a problem into smaller subproblems, solves each subproblem once, and stores the result so it never needs to be recomputed. It is not a single algorithm — it is a strategy that applies whenever a problem has two properties:

1. Overlapping Subproblems

A naive recursive solution solves the same subproblem many times. Think of computing Fibonacci numbers: to get fib(5) you need fib(3) twice and fib(2) three times. DP stores each result so it is computed only once.

2. Optimal Substructure

The optimal solution to the whole problem can be constructed from optimal solutions to its subproblems. For example, the shortest path from A to C through B requires the shortest path from A to B and the shortest path from B to C. If you can express the answer for a larger problem in terms of answers to smaller problems, you have optimal substructure.

When both properties are present, DP can transform an exponential brute-force solution into a polynomial one — often from O(2ⁿ) to O(n) or O(n²).

When to Use DP: 3 Signals to Recognize

In an interview you have seconds, not minutes, to decide on an approach. Watch for these three signals:

  1. The problem asks "how many ways" or "find the minimum/maximum." Counting and optimization problems almost always have overlapping subproblems.
  2. You can express the answer recursively. If you can write a recurrence like f(n) = f(n-1) + f(n-2), DP is likely the right tool.
  3. A brute-force recursion would revisit the same state. Draw two or three levels of the recursion tree. If you see repeated nodes, DP will help.

Keywords to watch for: "maximum profit", "minimum cost", "number of ways", "longest subsequence", "shortest path", "partition", "subset sum", "choose or skip".

Top-Down vs Bottom-Up

There are two ways to implement DP. Both give the same result; the choice depends on the problem and your comfort level.

Top-Down (Memoization)

Write the natural recursive solution, then add a cache (usually a dictionary or array) that stores each result the first time it is computed. On subsequent calls with the same arguments, return the cached value instead of re-computing.

def fib(n, memo={}):
    if n <= 1:
        return n
    if n in memo:
        return memo[n]
    memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
    return memo[n]

Pros: Mirrors the recurrence directly, easy to derive from brute force.
Cons: Recursion stack can overflow for very large inputs; slight overhead per function call.

Bottom-Up (Tabulation)

Build the solution iteratively from the smallest subproblem up to the target. Fill a table (array) in order so that every value you need has already been computed by the time you need it.

def fib(n):
    if n <= 1:
        return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]

Pros: No recursion overhead, no stack overflow risk, easier to optimize space.
Cons: You must figure out the correct fill order, which can be tricky for multi-dimensional DP.

Beginner tip: Start with top-down (memoization) because it maps directly to the recursive formula. Once you are comfortable, convert to bottom-up for better performance and space optimization.

The 6 DP Sub-Patterns

Most DP problems in interviews fall into one of six families. Learning these templates lets you recognize a new problem and immediately know the state definition, recurrence, and base case.

1. Fibonacci Pattern

Each state depends on the previous one or two states. The recurrence looks like dp[i] = dp[i-1] + dp[i-2]. Space can often be optimized to O(1) since you only need the last two values.

Classic problems: Climbing Stairs, House Robber, Decode Ways, Tribonacci Number.

View Fibonacci Pattern →

2. 0/1 Knapsack Pattern

For each item you make a binary decision: take it or skip it. There is a capacity constraint. The 2D recurrence is dp[i][w] = max(dp[i-1][w], dp[i-1][w-wt[i]] + val[i]). You can optimize to 1D by iterating the weight dimension in reverse.

Classic problems: 0/1 Knapsack, Partition Equal Subset Sum, Target Sum, Last Stone Weight II.

View 0/1 Knapsack Pattern →

3. Unbounded Knapsack Pattern

Same structure as 0/1 Knapsack, but each item can be used unlimited times. The key difference: iterate the capacity dimension forward instead of backward, allowing the same item to be picked again.

Classic problems: Coin Change, Coin Change II, Perfect Squares, Rod Cutting.

View Unbounded Knapsack Pattern →

4. Longest Common Subsequence (LCS) Pattern

Involves two sequences and uses a 2D table. Each cell represents a comparison of prefixes. If characters match, extend the result; otherwise, take the best from either direction: dp[i][j] = dp[i-1][j-1] + 1 or max(dp[i-1][j], dp[i][j-1]).

Classic problems: Longest Common Subsequence, Edit Distance, Longest Palindromic Subsequence, Shortest Common Supersequence.

View LCS Pattern →

5. DP on Trees

Combines tree structure with optimization. Uses post-order DFS so that each node computes its answer from its children. Often you need to track two values per node: the best answer that extends upward and the best answer that passes through the node.

Classic problems: Binary Tree Maximum Path Sum, House Robber III, Diameter of Binary Tree, Longest Path in Tree.

View DP on Trees Pattern →

6. Grid DP

You traverse a 2D grid and each cell's answer depends on the cell above and/or to the left. The first row and first column are base cases. Often optimizable to a single 1D array by processing row by row.

Classic problems: Unique Paths, Minimum Path Sum, Maximal Square, Dungeon Game, Cherry Pickup.

View Grid DP Pattern →

Step-by-Step Example: Climbing Stairs

Problem: You are climbing a staircase with n steps. Each time you can climb 1 or 2 steps. In how many distinct ways can you reach the top?

Step 1: Identify the DP Signals

  • "How many distinct ways" — counting problem.
  • The number of ways to reach step n depends on steps n-1 and n-2 — optimal substructure.
  • A naive recursion would recompute the same steps many times — overlapping subproblems.

Step 2: Brute-Force Recursion (Exponential)

def climb(n):
    if n <= 1:
        return 1
    return climb(n - 1) + climb(n - 2)

# Time: O(2^n) — way too slow for n > 30

Step 3: Add Memoization (Top-Down)

def climb(n, memo={}):
    if n <= 1:
        return 1
    if n in memo:
        return memo[n]
    memo[n] = climb(n - 1, memo) + climb(n - 2, memo)
    return memo[n]

# Time: O(n), Space: O(n)

Step 4: Convert to Bottom-Up (Tabulation)

def climb(n):
    if n <= 1:
        return 1
    dp = [0] * (n + 1)
    dp[0] = 1
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]

# Time: O(n), Space: O(n)

Step 5: Optimize Space to O(1)

Since each state only depends on the two previous states, we can replace the array with two variables:

def climb(n):
    if n <= 1:
        return 1
    prev2, prev1 = 1, 1
    for i in range(2, n + 1):
        curr = prev1 + prev2
        prev2 = prev1
        prev1 = curr
    return prev1

# Time: O(n), Space: O(1) — optimal!

This four-step progression — brute force, memoization, tabulation, space optimization — works for virtually every DP problem. Practice it until it becomes automatic.

Step-by-Step Example: 0/1 Knapsack

Problem: Given n items, each with a weight and a value, determine the maximum value you can carry in a knapsack of capacity W. Each item can be taken at most once.

Define the State

Let dp[i][w] = the maximum value achievable using items 0..i with remaining capacity w.

Write the Recurrence

For each item i with weight wt[i] and value val[i]:

  • Skip item i: dp[i][w] = dp[i-1][w]
  • Take item i (if wt[i] <= w): dp[i][w] = dp[i-1][w - wt[i]] + val[i]
  • Take the maximum of both choices.

Bottom-Up Implementation

def knapsack(weights, values, capacity):
    n = len(weights)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]

    for i in range(1, n + 1):
        for w in range(capacity + 1):
            dp[i][w] = dp[i - 1][w]          # skip item i
            if weights[i - 1] <= w:
                dp[i][w] = max(
                    dp[i][w],
                    dp[i - 1][w - weights[i - 1]] + values[i - 1]
                )

    return dp[n][capacity]

# Time: O(n * W), Space: O(n * W)

Space-Optimized 1D Version

Since each row only depends on the previous row, we can use a single 1D array. The trick: iterate w in reverse so that we don't overwrite values we still need:

def knapsack(weights, values, capacity):
    n = len(weights)
    dp = [0] * (capacity + 1)

    for i in range(n):
        for w in range(capacity, weights[i] - 1, -1):
            dp[w] = max(dp[w], dp[w - weights[i]] + values[i])

    return dp[capacity]

# Time: O(n * W), Space: O(W) — much better!

Notice the reverse iteration on w. This is the hallmark of 0/1 Knapsack. In the Unbounded Knapsack pattern, you iterate forward instead, which allows an item to be picked multiple times.

Common DP Mistakes (and How to Avoid Them)

Mistake 1: Wrong Base Case

The most common bug. Always ask: "What is the answer when the input is empty, zero, or has one element?" Write the base case before the recurrence. For example, in Climbing Stairs, dp[0] = 1 (one way to stay at the ground) is easy to get wrong if you don't think carefully.

Mistake 2: Incorrect State Definition

If your state doesn't capture enough information, the recurrence will be wrong. For Knapsack, a 1D state dp[i] is not enough — you need dp[i][w] to track the remaining capacity. Always ask: "What changes between subproblems?"

Mistake 3: Wrong Iteration Order

In bottom-up DP, the fill order matters. You must ensure that every value you read from the table has already been computed. In 0/1 Knapsack, iterating weights forward instead of backward accidentally allows reusing items (turning it into Unbounded Knapsack).

Mistake 4: Forgetting to Initialize the Table

For minimization problems, initialize the DP table with infinity (float('inf')) instead of zero. For counting problems, make sure the base case count is 1, not 0. Getting the initial values wrong will silently produce incorrect answers.

Mistake 5: Using DP When Greedy Works

Not every optimization problem needs DP. If the problem has the greedy-choice property (a locally optimal choice is globally optimal), greedy is simpler and faster. Activity Selection and Fractional Knapsack are greedy, not DP. Check for the greedy property first.

Practice Problems by Sub-Pattern

The best way to internalize DP is to solve problems grouped by pattern. Here are curated problems for each sub-pattern, ordered from easier to harder:

Fibonacci Pattern

  1. Climbing Stairs (LC 70)
  2. House Robber (LC 198)
  3. House Robber II (LC 213)
  4. Decode Ways (LC 91)
  5. Minimum Cost Climbing Stairs (LC 746)
Study Fibonacci Pattern →

0/1 Knapsack Pattern

  1. Partition Equal Subset Sum (LC 416)
  2. Target Sum (LC 494)
  3. Last Stone Weight II (LC 1049)
  4. Ones and Zeroes (LC 474)
Study 0/1 Knapsack Pattern →

Unbounded Knapsack Pattern

  1. Coin Change (LC 322)
  2. Coin Change II (LC 518)
  3. Perfect Squares (LC 279)
  4. Integer Break (LC 343)
Study Unbounded Knapsack Pattern →

LCS Pattern

  1. Longest Common Subsequence (LC 1143)
  2. Longest Palindromic Subsequence (LC 516)
  3. Edit Distance (LC 72)
  4. Shortest Common Supersequence (LC 1092)
  5. Distinct Subsequences (LC 115)
Study LCS Pattern →

DP on Trees

  1. Diameter of Binary Tree (LC 543)
  2. Binary Tree Maximum Path Sum (LC 124)
  3. House Robber III (LC 337)
  4. Longest Univalue Path (LC 687)
Study DP on Trees Pattern →

Grid DP

  1. Unique Paths (LC 62)
  2. Minimum Path Sum (LC 64)
  3. Maximal Square (LC 221)
  4. Dungeon Game (LC 174)
  5. Cherry Pickup (LC 741)
Study Grid DP Pattern →

Further Reading

Ready to Master Dynamic Programming?

AlgoArk has interactive walkthroughs, animated visualizations, and pattern-based quizzes for every DP sub-pattern — Fibonacci, Knapsack, LCS, Grid DP, and more.

Explore All DP Patterns