Skip to Content
Course content

178: Dynamic Programming Patterns in JavaScript

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years back, I was reviewing a PR from a junior dev who was building a path-finding feature for a grid-based puzzle game. He had written a clean, elegant recursive function to find the number of unique paths from the top-left to the bottom-right of a grid. It looked beautiful in the code editor. Then, he tried to run it on a 20x20 grid, and his entire Chrome tab froze. He was baffled because the logic was technically correct, but the execution was a disaster. The problem was that his function was recalculating the same coordinates millions of times, blindly repeating work it had already finished. This is exactly where Dynamic Programming (DP) saves your skin.

At its heart, DP isn't some mystical mathematical ritual; it's just a fancy term for "remembering stuff so you don't have to do it again." When you have a problem that can be broken down into smaller, overlapping sub-problems, you have two main ways to handle it in JavaScript: memoization and tabulation. If you've already mastered recursion, you're halfway there.

Stopping the Recursive Spiral with Memoization

Memoization is the "top-down" approach. You keep your recursive structure, but you introduce a cache—usually a JavaScript object or a Map—to store the results of function calls. Before the function does any heavy lifting, it checks the cache. If the answer is already there, it returns it immediately. If not, it calculates it, saves it, and then returns it.

Take a look at how we can fix that grid path problem. Instead of just calling the function recursively, we pass a memo object along:

function countPaths(r, c, memo = {}) {
  const key = `${r},${c}`;
  if (key in memo) return memo[key];
  if (r === 0 || c === 0) return 1;

  memo[key] = countPaths(r - 1, c, memo) + countPaths(r, c - 1, memo);
  return memo[key];
}

The difference here is night and day. Without the memo, the time complexity is exponential. With it, it becomes linear relative to the number of cells in the grid. I usually prefer memoization when the state space is sparse—meaning you don't actually need to visit every single possible combination to get your answer.

Building from the Ground Up with Tabulation

Then there's tabulation, the "bottom-up" approach. Instead of starting at the end and drilling down via recursion, you start at the smallest possible sub-problem and fill out a table (usually a 2D array) until you reach your target. This completely avoids the overhead of the call stack, which means you won't run into "Maximum call stack size exceeded" errors on massive inputs.

In the same grid scenario, tabulation looks like this:

function countPathsTabulated(rows, cols) {
  const table = Array(rows).fill().map(() => Array(cols).fill(1));

  for (let r = 1; r < rows; r++) {
    for (let c = 1; c < cols; c++) {
      table[r][c] = table[r - 1][c] + table[r][c - 1];
    }
  }

  return table[rows - 1][cols - 1];
}

I find tabulation is often more performant in JavaScript because iterating through arrays is significantly faster than jumping around the stack with recursive calls. It requires a bit more foresight to determine the order of computation, but it's the gold standard for production-grade DP algorithms.




📋 Practical Task

Implementing the Minimum Coin Change Optimizer

You are tasked with writing a function minCoins(coins, amount). Given an array of coin denominations (e.g., [1, 5, 10, 25]) and a target amount, find the minimum number of coins needed to make that amount. If the amount cannot be reached, return -1.

Requirements:

  • Do not use a naive recursive approach; it will time out for large amounts.
  • Implement your solution using either a memoization object or a tabulation array.
  • Test your function with minCoins([1, 2, 5], 11) (should return 3: 5+5+1) and minCoins([2], 3) (should return -1).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.