Skip to Content
Course content

35: Recursion in JavaScript

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

I'll be honest with you: recursion is one of those concepts that feels like a "eureka" moment when it first clicks, but it's also one of the easiest ways to accidentally crash your application. At its simplest, recursion is just a function calling itself. It's a beautiful way to handle data structures where you don't know how deep the nesting goes—like a file system or a nested comment thread on a blog.

The Elegance of the Naive Approach

Let's look at a classic problem: flattening a deeply nested array. You might have an array like [1, [2, [3, [4]]]] and you want it to be [1, 2, 3, 4]. The naive recursive approach is incredibly clean. You just check if an element is an array; if it is, you call the function again on that element. If it's not, you keep it.

function flattenNaive(arr) {
  let result = [];
  arr.forEach(item => {
    if (Array.isArray(item)) {
      result = result.concat(flattenNaive(item));
    } else {
      result.push(item);
    }
  });
  return result;
}

When you look at this code, it feels "correct." It's declarative and mirrors the structure of the data. For most small-to-medium datasets, this works perfectly. You write it in five minutes, it passes the tests, and you move on. But this is where the "junior" mistake happens: assuming that because it works for 10 levels of nesting, it will work for 10,000.

When the Call Stack Hits a Wall

Here is the problem I've run into in production more than once: the Call Stack. Every time JavaScript calls a function, it adds a "frame" to the stack to remember where it was and what the local variables were. In the naive version above, the function can't actually finish and return its result until the inner recursive call finishes.

If you pass an array nested 20,000 levels deep into flattenNaive, you aren't just doing a lot of work; you're asking JavaScript to remember 20,000 different unfinished function calls. Eventually, the engine gives up and throws a RangeError: Maximum call stack size exceeded. You've hit the ceiling. It's a hard crash, and there's no try...catch that's going to make that recursion suddenly "fit" in memory.

Trading Elegance for Stability

If you're working with potentially massive datasets, I usually advise my colleagues to ditch the recursive call in favor of a manual stack. Instead of letting the JavaScript engine manage the "to-do list" via the call stack, you manage it yourself using a standard array. This moves the memory usage from the call stack (which is very small) to the heap (which is much larger).

function flattenIterative(input) {
  const stack = [...input];
  const result = [];
  
  while (stack.length > 0) {
    const next = stack.pop();
    if (Array.isArray(next)) {
      // Push contents back onto the stack to be processed
      stack.push(...next);
    } else {
      result.push(next);
    }
  }
  
  // Since we popped from the end, we need to reverse the result
  return result.reverse();
}

Now, look at the trade-off. We lost that "mathematical" beauty of the first example. The code is a bit more verbose and requires a .reverse() at the end because we're processing the array from the back. But here's the win: this version can handle an array nested a million levels deep without breaking a sweat. It doesn't add new frames to the call stack; it just modifies a single array in memory.

My rule of thumb? Use recursion when the depth is guaranteed to be shallow (like a UI component tree that's rarely more than 10 levels deep). But the moment you're dealing with user-generated data or massive datasets, reach for the iterative approach. Your on-call rotation will thank you.




📋 Practical Task

Exercise: Deeply Nested Category Tree Flattener

You are building an e-commerce site that has categories and sub-categories nested to an unknown depth. You've been given a "Category Tree" object, but the search index requires a flat list of all category names.

The Data:

const categoryTree = [
  {
    name: "Electronics",
    children: [
      {
        name: "Laptops",
        children: [
          { name: "Gaming Laptops", children: [] },
          { name: "Ultrabooks", children: [] }
        ]
      },
      { name: "Cameras", children: [] }
    ]
  },
  {
    name: "Clothing",
    children: [
      { name: "Mens", children: [] },
      { name: "Womens", children: [] }
    ]
  }
];

Your Task: Write a function called getAllCategoryNames that takes this tree and returns a flat array of strings: ["Electronics", "Laptops", "Gaming Laptops", "Ultrabooks", "Cameras", "Clothing", "Mens", "Womens"].

Constraint: To ensure the application doesn't crash on extremely large catalogs, implement this using a manual stack (iterative approach) rather than recursion.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.