A few minor style suggestions for starters:
The bigger issue is the presence of two really common antipatterns in recursion -- I'd call these almost universal "beginner recursion" mistakes (but don't take that the wrong way; I've made these mistakes too and still do!).
The first is the tendency to make decisions about root.left
and root.right
from the parent frame. In general, it adds complexity, isn't necessary and disrupts the magical part of recursion: after you code up the logic for your frame's data, let the children apply the same pattern and hand you their results. Don't dip into and mess with the children's data unless you must. If you find you have to, then you may have formulated the problem incorrectly or recursion may not the correct pattern to apply to the problem.
Better is to let the child calls handle the parent's root.left?
as root?
itself, after the call to maxDepth(root.left)
. This simplifies the code quite dramatically and is more natural:
const maxDepth = root => root ? 1 + Math.max(maxDepth(root.left), maxDepth(root.right)) : 0 ; /* 3 / \ / \ 9 20 / \ 15 7 */ const tree = { val: 3, left: {val: 9}, right: { val: 20, left: {val: 15}, right: {val: 7}, }, }; console.log(maxDepth(tree));
This code should reveal the second common/universal recursion mistake I see: overcomplicating return values and parameters. Generally speaking, any given piece of data flows one way: results flow back up the tree and state flows down.
If you're passing the same data representing the results down and then back up again without really operating on it, as is the case in your code, oftentimes the downward-flowing data is pointless. You can see I've done away with the unnecessary parameter in favor of the following recursive logic:
- If I'm a call frame with an empty node, the depth is 0 so return 0
- Otherwise, return 1 for my node plus the larger of whatever values my children have.
That's it!
By the way, adding the result as a parameter isn't just an issue of reducing verbosity (although that's reason enough for me) -- you can actually wreck your memoization attempt with it on dynamic programming problems, so it's quite critical to be clear about what the dependencies are in the DAG of problems you're solving. If it's not a dependency (i.e. it's a result, or irrelevant information), then it has to be taken out of the memoization table.
Here's a concrete example of this over on Stack Overflow: Issue with memoization - House Robber problem. If you haven't done DP yet, no problem -- the point is that adding complexity is usually worse than an aesthetics issue.
So, to summarize how to avoid these recursive antipatterns:
- Try not to touch child properties anywhere except to set up your recursive calls
- Generally don't use parameters as return values
- If the solution seems over-complicated, it probably is.