-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
178: Dynamic Programming Patterns in JavaScript
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) andminCoins([2], 3)(should return -1).
There are no comments for now.