JS Interview Help (pt 2): Hoisting, Currying, and Memoization

Michael Bade
4 min readNov 5, 2020

Please note — this is a BRIEF explanation, meant for 2–3 minute interview answers. Please use the resources after the article for a deeper understanding on each topic.

Hoisting

When a piece of JS code is executed, the JS engine makes a global execution context, which has two phases: creation and execution. In creation, the JS engine moves the variable and function declarations to the top of your code. In other words, it hoists these declarations to the top of your code.

Hoisting is JavaScript’s default behavior of moving declarations to the top. SO, just like my other article on JS interview questions, I’ll use another childhood dog for examples. Let’s take a look at the following code:

console.log(toby);
let toby = "My name is Toby, I'm the goodest dog, and I want to be first in line for pets!";
//=> VM246:1 Uncaught ReferenceError: toby is not defined
at <anonymous>:1:13

Really, the above code looks like this to the interpreter:

let toby;
console.log(toby);
toby = "My name is Toby, I'm the goodest dog, and I want to be first in line for pets!";

JavaScript only hoists the declaration (let toby;) and not the initialization… So, once functions and variables are declared, they’re moved to the top of their scope — whether that is local or global.

The standard for developers is to declare your variables at the top of their defined scope. So, if I were to write a function about Toby, the variables within that function should be at the top of their scope:

let tobyIsFirstInLineForPets = () => {
let first = "first";
let last = "all other doggos";
console.log(`Toby is ALWAYS ${first} in line, in front of ${last}
for pets.`);
}
tobyIsFirstInLineForPets();
//=> Toby is ALWAYS first in line, in front of all other doggos for pets.

Currying

In short, currying is a process in functional programming where we can transform a function with multiple arguments into a sequence of nesting functions. It returns a new function that expects the next argument inline.

Basically, it keeps returning a new function until all arguments are met. Let’s dive into an example. Toby loved digging holes in the backyard, and we need to count how many holes he’s dug. We have four sections in the backyard, labeled by compass directions: North, South, East, and West. We could write our function like this:

function countTobysDugHoles(north, south, east, west) {
return north + south + east + west;
}

The above works, but with currying we can do the following:

function countTobysDugHoles(north) {
return (south) => {
return (east) => {
return (west) => {
return north + south + east + west;
}
}
}
}

We’ve turned our countTobysDugHoles(north, south, east, west) function call to countTobysDugHoles(north)(south)(east)(west) multiple function calls.

Why currying?

Basically, all partial applications can take as many or as few arguments a time, as desired. Curried functions, however, always return a unary function, or a function which takes one argument. It helps create a higher-order function, and is very helpful in event handling.

Memoization

Memoization is: an optimization technique that speeds up applications by storing the results of expensive function calls and returning the cached result when the same inputs are supplied again.

Basically, it’s the process of simplifying the amount of time and memory for function calls, through the use of cache. By caching the values that the function returns after its initial execution, we can access cached data, versus maker a full function call, and our data requests are served faster

When we input the same value into our memoized function, it returns the value stored in the cache instead of running the function again, thus boosting performance. Your program doesn’t have to recalculate every number to get a result. Let’s say, for instance, that Toby has 1,000 toys, and we’re organizing his toys:

let cache = {};function addingTobysToys(toys) {
if(toys in cache) {
let splitted = cache[toys].split(' ');
let sliced = splitted.slice(0, 2).join(' ');
return sliced + ' toy is already in your bin.';
} else {
console.log('Adding a new toy!');
return cache[toys] = toys + ' toy has been added.';
}
}
console.log('Toy 1: ', addingTobysToys("Blue Ball"))
//=> Adding a new toy!
Toy 1: Blue Ball toy has been added.
console.log('Toy 2: ', addingTobysToys("Red Dinosaur"))
//=> Adding a new toy!
Toy 2: Red Dinosaur toy has been added.
console.log('Toy 3: ', addingTobysToys("Green Turtle"))
//=> Adding a new toy!
Toy 3: Green Turtle toy has been added.

As you can see, since cache[toys] does not contain one of these toys, they are being added to cache. What’s going to happen when we run:

console.log('Toy 1: ', addingTobysToys("Blue Ball"))

Since this toy is already in cache, the following is printed:

//=> Toy 1:  Blue Ball toy is already in your bin.

Rather than making a call to find this piece of data in Toby’s toy bin (which, since he’s such a good boy, has 1,000+ toys in it), we can use memoization to store and grab data without the weight of pulling every single toy out of the toy bin.

Resources

Hoisting

Currying

Memoization

Thanks for reading! To view Michael’s portfolio, click here. Michael is a recent Flatiron School graduate, open for work, and always happy to talk code. Let’s connect on LinkedIn! Questions or comments are always welcome!

For part 1 of this series on JavaScript interview help, click here.

--

--

Michael Bade

Michael Bade is a Full Stack Web Developer, with a passion for making abstract ideas come to life! Find me on LinkedIn to connect and talk code!