What survives

Does obfuscation break recursion?

A recursive function calls itself by name, which puts it directly in the path of the transform obfuscators perform most often. The named function expression makes the question sharper still, because it uses a name that exists nowhere else in the program. Measured against the real engine in five configurations, every case came out identical, and stack depth was measured rather than assumed.

Why recursion feels like the direct test of renaming

A recursive call is a self-reference by name. Where ordinary code names things so that other code can find them, a recursive function names itself so that it can find itself, and it does so from inside its own body while it is running.

If a rename ever lost track of one of those references, the failure would be immediate and total: the function would call something that no longer exists. That is a loud failure rather than a quiet one, which is a mercy, but recursion also tends to appear in the parts of a codebase where a total failure is worst, such as tree walkers, serialisers, parsers and directory traversals.

The named function expression is the version worth focusing on, because it is the only common construct where a name is visible in exactly one place: inside the function itself. Everything else in a program has at least two mentions.

What was measured

A single sample was written to exercise the surface in one file: a plain recursive factorial; a named function expression recursing by its own name, plus a check that the name does not leak into the enclosing scope; a recursive function held in a variable that is reassigned afterwards, to test which binding the call actually resolves to; mutual recursion between two declarations; recursion over a tree, both summing values and computing depth; a memoised Fibonacci built as a named function expression inside a closure; bounded recursion two thousand frames deep; a runaway recursion caught as a RangeError; recursion inside a method that reads this; an accumulator-style recursion; indirect recursion through a function stored in an array; recursive string building; and a recursive flatten over a nested array.

The file was run in Node, then protected and run again, with the two outputs compared line by line, across the same five configurations used elsewhere in this series, plus a member-renaming column driven with a pattern scoped to the sample's own property names.

All five configurations matched the original exactly, and so did both member-renaming builds. Mutual recursion still agreed on parity. The memoised Fibonacci still produced the right value at n equal to 30, which it could not do without the memo closure working. The tree walk still reported the same sum and the same depth.

The named function expression is the case that actually tests it

The interesting result is the one about scope rather than the one about arithmetic.

When you write a function expression with a name, that name is bound inside the function's own scope and nowhere else. It exists so the function can call itself, and it is deliberately invisible to the surrounding code. The sample checked both halves: the recursion by that inner name produced the right value, and reading the name from outside the function reported undefined, confirming it never leaked.

Both halves survived protection in every configuration. The inner name is renamed together with the recursive call that uses it, because they are one binding and one reference in the same scope, and it still does not leak outward afterwards. This is the same scope-aware property that keeps closures intact, applied to the smallest possible scope.

The reassignment case is the sharp end of the same point. A function stored in a variable and calling itself through that variable is not recursing by an inner name; it is reading a variable that other code can change. The sample reassigned the variable after taking a reference, called through the old reference, and got the replacement function, before protection and after it. That is correct behaviour and a genuine hazard in unprotected code too, and it is the reason a named function expression is the more robust way to write a recursive helper.

Stack depth, measured rather than assumed

The obvious follow-up question is whether protection costs you stack depth, since a transform that added a wrapper frame per call would reduce how deep a recursion can go before it overflows.

This was measured rather than reasoned about. A probe recursed until it caught a RangeError and reported which bucket the depth fell into. The original and all four protected configurations landed in the same bucket, above three thousand frames. Exact frame counts are not stable enough between runs to quote, which is why a bucket is the honest unit here, but the result is clear enough for the practical question: no configuration moved the ceiling into a range where ordinary recursive code would newly fail.

The bounded case in the main sample agrees. A recursion two thousand frames deep completed normally in every configuration, and the runaway recursion still terminated as a RangeError rather than as any other kind of failure, so error handling written around stack overflow still catches what it expects.

If your code recurses to a depth where the ceiling is a live concern, that is worth knowing regardless of protection, and the answer is usually to convert the hot path to an explicit stack rather than to tune the build.

What protection does not change about a recursive algorithm

Two things are worth stating plainly, because recursion attracts performance folklore.

Protection does not change the complexity of your algorithm. A memoised Fibonacci is fast because of the memo, and it was still fast after protection because the closure holding the memo still worked. An unmemoised one would be slow before and after. No transform in the pipeline converts one into the other, and none of them changes how many times your function calls itself.

Protection also does not introduce tail call optimisation, and neither does the platform in practice. An accumulator-style recursion is written in a shape that could in principle be optimised into a loop, and it is not, before or after. If you wrote it that way expecting the optimisation, you are relying on something that is not there in the engines you are shipping to, and protection is not the reason.

What does change is readability of the protected output, which is the point. A renamed recursive function is harder to follow at a glance, and the structure of the recursion is still visible to anyone who steps through it. That is the usual boundary: reading cost goes up, and the algorithm is still the algorithm.

Scope, and how to check your own build

This article covers direct recursion, mutual recursion, named function expressions, recursion through closures and methods, and stack depth, measured on the default configuration and the four variants named above plus a member-renaming column. It does not cover generators or lazy iteration, which are a different mechanism, nor asynchronous recursion built on promises, which turns on scheduling rather than naming.

Checking your own build is short. Take the recursive helpers your project actually ships, in particular any tree walk or serialiser, run them over a fixture that produces deterministic output, and print the result. Protect with the configuration you really use, run both files under Node, and compare.

If you use member renaming, include a case where the recursion walks your own object structure, since the property names it reads on the way down are the part that renaming touches. In this sample a member pattern scoped to the sample's own property names produced identical output on both targets.

Frequently asked questions

Does obfuscation break recursion?

No. Measured across five configurations plus a member-renaming column, every recursive case came out identical: direct recursion, mutual recursion, tree walks, memoised recursion through a closure, recursion inside a method reading this, and indirect recursion through a function stored in an array.

Does a named function expression still recurse by its inner name?

Yes, and this is the case that most directly tests renaming, because the inner name exists in exactly one place. The recursion produced the correct value after protection, and the name still did not leak into the enclosing scope, reporting undefined when read from outside.

Does protection reduce how deep recursion can go?

Not measurably. A probe that recursed until it caught a RangeError put the original and all four protected configurations in the same bucket, above three thousand frames. A bounded recursion two thousand frames deep completed normally in every configuration.

Does a stack overflow still throw a RangeError after protection?

Yes. A runaway recursion still terminated as a RangeError rather than as some other failure, so error handling written to catch stack exhaustion still catches what it expects.

Does protection make a recursive algorithm faster or slower?

It does not change the algorithm. A memoised recursion was still fast after protection because the closure holding the memo still worked, and an unmemoised one would be slow either way. Protection also does not add tail call optimisation, which is absent in practice before and after.

Is recursion through a reassignable variable safe?

It is unchanged by protection, but it is fragile in the language itself. A function that calls itself through an outer variable resolves that variable at call time, so reassigning it changes what the recursion calls. That was true before and after protection, which is a good reason to prefer a named function expression for recursive helpers.

Related reading