Motivation
I'm applying and interviewing for jobs right now and consequently brushing up on algorithms and data structures. This has led me to a lot of tinkering with recursion and backtracking. I'm a kinesthetic learner and keeping track of what's going on deep in a recursion stack makes my brain tired - so practice! This is NOT a blog about recursion and backtracking, although one of those may be here soon (In the meantime here is a pretty good article on the subject.
All this practice has brought my attention to a Javascript feature (quirk) that I often forget about in these situations - Javascript's passing of values or references depending on the data type.
Reference Vs. Value
When passing a variable that points to an object a reference to that object gets passed. When passing a variable that points to a number, boolean, string, or undefined the value of the variable is passed. Practically this means assigning multiple variables to an object will allow all of those variables to access the same object. This is not true with values. Quick example:
let x = "Cheddar";
let y = x;
y = "Manchego"
console.log(x) //"Cheddar"
console.log(y) //"Manchego"
let x = ["Cheddar"];
ley y = x;
y.push("Manchego");
console.log(x); //["Cheddar", "Manchego"]
console.log(y); //["Cheddar", "Manchego"]
We can get around this by using the spread operator:
let x = ["Cheddar"];
ley y = [...x];
y.push("Manchego");
console.log(x); //["Cheddar"]
console.log(y); //["Cheddar", "Manchego"]
Importance for Recursion
Okay, that's all well and good but what does this have to do with recursion? Well, to be perfectly honest, not much, but for me, it keeps coming up when refactoring iteration to recursion or visa-versa.
Let's take a look at the quintessential recursion example: Fibonacci (More info on the Fibonacci sequence available on the always helpful Wikipedia).
Here's a quick function to return the nth term in the Fibonacci sequence:
function fibonacci(n) {
const dict = {};
return calcFib(n, dict);
}
function calcFib(n, dict) {
if (n === 1 || n === 2) {
return 1;
}
if (dict[n]) return dict[n];
result = calcFib(n - 1, dict) + calcFib(n - 2, dict);
dict[n] = result;
return result;
}
Notice that at every return we have to return the result
. If we had chosen to make result
an argument of calcFib
, we would still need to return the result
of the calculation. This is because when we pass down result
to another instance of calcFib
it is just the value result
points to not the result
we will eventually return. Another way to look at this is through the lens of our memoization dictionary, dict
. We never return this value yet it remains updated through all instances of calcFib
. This happens because dict
is an object and so we are updating a reference to the location of dict
in memory, not just the values contained in dict
.
Let's look at a non-recursive version to see if we can clear this up. Here is an iterative function to return an array of the first n terms of the Fibonacci sequence.
function calcFib(current, previous, result) {
result.push(current + previous);
}
function fibonacci(n) {
let result = [];
//base cases
if (n === 1) return result.push(1);
if (n >= 2) {
result.push(1);
result.push(1);
}
for (let i = 1; i < n - 1; i++) {
calcFib(result[i], result[i - 1], result);
}
return result;
}
Notice that in calcFib
we don't return anything! We can get away with this because we are updating an array (which in Javascript is a type of object) and that means we are passing calcFib
a reference to the result
array. When we add another value of the sequence to result
we are always updating the same object in memory.
Final thoughts
The 5-cent takeaway here: in Javascript objects are passed by reference, meaning they always point to the same object in memory even if being passed to another function with a different scope. Everything else is passed by value, so if you are entering another scope and you want an updated value back make sure to return it!
Thanks for reading and hope this saves someone a bit of debug time.
Top comments (1)
This can be a powerful tool if you keep it in mind, or an unsolvable riddle if you don't. It makes me wish JavaScript just explicitly used pointers and passes-by-reference. It's all happening under the hood anyway; just give us some transparency and finer-grained control!