Factorial by Proxy

2024-04-16

A cool snippet I came up with for calculating recursively defined sequences with built-in memoization.

const factorial = new Proxy([1], { get(f, n) { return f[n] ?? (f[n] = this.get(f, n-1) * n); } });

Use like this:

factorial[0] // 1 factorial[1] // 1 factorial[2] // 2 factorial[3] // 6 // ...

Explanation:

The proxy allows us to overwrite property access on the array using a custom get function, which we abuse use here to get a seemingly infinite array containig the entire sequence.

Within it, we can set and get properties of the underlying array normally: f[k] = 3 and return f[n] work as expected (the array is available as f). However, for recursion to work we want to use our new get, so we use this.get(f, k) to call the new getter recursively, calculating unknown (undefined) values along the way.

Our get function does the following: if f[n] is already set—i.e. not undefined (or false)—it is returned instantly. Otherwise, we calculate it recursively, assign the calculated value to f[n] and return that value.

Note that we don’t overwrite anything to do with iterators, so factorial.forEach(...) will only iterate over previously calculated values. If you want to be fancy, you could make it so that iterating works, that the underlying array cannot be modified other than from within the getter and maybe some handling for very large inputs or inputs < 0.

Judging by the browser compatibility for handler.get() listed on mdn, this should work everywhere in browsers updated since 2016.

Other sequences:

Here’s how it looks for the Fibonacci numbers:

const fibonacci = new Proxy([0,1], { get(f, n) { return f[n] ?? (f[n] = this.get(f, n-2) + this.get(f, n-1)); } }); fibonacci[0] // 1 fibonacci[1] // 1 fibonacci[2] // 2 fibonacci[3] // 5 // ...

Build your own:

const yourSequence = new Proxy([/* array containing the first elements of the sequence */], { get(f, n) { return f[n] ?? (f[n] = /* formula to calculate the n-th element, use `this.get(f, k)` instead of `f[k]` or `yourSequence[k]` */); } });