Background
Do you remember how we used to play Nintendo games as kids, and did you ever played Need for speed video games. Boy! I don't know what you played if you didn't played that game, and can you remember how you try to boost your racing speed by the push of a button to apply some nitro super powers? Yeahhh, I know you are having memories already, good old days huh!
Anyways, it is pretty much the same concepts applied in programming, but just in a different context, and in this article I aim to explain the relationship between nitrospeeding concept from the video game and memoization for your computer programs.
When we write code and build applications, we often write functions that tend to perform some sort of computations, and those computations could be very expensive, in terms of space (as in the amount of memory needed by your program to execute) and time (as in the duration it takes for your program to execute) complexity.
This can affect the performance of your application, and as such, you might loose your customers, some money, as a result or even worse. So, what if there is a way to make those slow performing processes that makes your application lack some Nitro Speed faster and better?
Let's do this thing!
What is Memoization?
That is when MEMOIZATION gains the spotlight, because it essentially aims to solve the issues with performance for the most. When your application slows down, it might be because of a particular process running that causes the delay to affect the entire app, and when we build software's we tend to use functions, because they make our programs reusable and modular. So, maybe you have a function that performs some kind of API calls or complex calculations that requires a lot of space and time to execute, then what you can do to prevent such function from affecting your application is to memoize the function, and that basically means that since the function will be receiving some inputs, and producing some results, then why not sort of remember the values of each input, so that whenever you need to perform a future action that depends on the values from the previous computations with already know inputs, then your function could just remember them and use them again easily rather than have to recalculate them all over again, and doing so will tremendously improve the speed and efficiency of your programs.
Essentially, think of memoization as a type of caching (where caching generally refers to storage techniques adopted, when you intend to reuse a resource in the future) where you memorize the values of known input, and should they be needed in the future, we could remember their outcomes, rather than calculate them again and again and again, which is a very tedious process and time consuming one also.
And that ladies and gentlemen is Memoization.
What can I memoize?
You can memoize essentially, almost all types of function, but should you actually memoize all functions? and the abrupt answer is no don't.
You can memoize :
- Pure functions (a pure function essential is a function that returns the exact same value for the exact same input everytime)
- Functions with limited input range but repeatedly occuring
- Functions that performs complex computations with repeated values, and also some API calls that are happening too frequent, but speaking of API calls, make sure to do a background check, because your browser is most likely using HTTP CACHING already to cache your accessed URLs in the browser.
Anything, aside from this, just be reasonable about it, and carefully think of the impact it might cause and the idea in principle for your applications.
What is the catch?
While there are a lot of useful articles online, explaining memoization, I often don't read about the side effects of applying this approach in your programs. So, in order to make that clear, I would like to inform you that memoization does a great job in terms of performance for your programs, but it does so in a trade for memory consumption, because you will need a lot of memory to store the previously computed values, and depending on the size and throughput of your transactions or computations, the variation in the amount of memory consumed will be reflected accordingly. Hence, keep that in mind when using this approach.
An Example
Enough talking, let's see some actions. And in this example, I will, show you how to create a simple greeter function that uses memoization to remember a previous user and displays a different message accordingly, and I am choosing this example just to demonstrate some really subtle instance, that is not all numerical and requires some computations like; fibonacci, squaring, summing, factorial, and just to mention a few, because you will see a lot of these examples online, but I just also want you to see a different use case and that you could apply the concept to pretty much any kind of function you wish, you can be really creative about it, so let's see some action.
const greet = () => {
let users = {}
return (name) => {
if (name in users) {
let message = name + ', you already seem to know your way around, please feel free to explore!'
users[name] = message
return message
}
else {
let message = 'Hello ' + name + ', it looks like you are new here, let\'s get you all setup shall we.'
users[name] = message
return message
}
}
}
let greeter = greet()
console.log(greeter('Dave')); // Hello Dave, it looks like you are new here, let's get you all setup shall we.
console.log(greeter('Dave')); // Dave, you already seem to know your way around, please feel free to explore!
console.log(greeter('Precious')); // Hello Precious, it looks like you are new here, let's get you all setup shall we.
Breakdown
Here in this example, you can see that we have a function that returns another function, which is something called a closure and it is important in order to make the function able to remember the values on consequent executions, unless this, it will just start a new execution every time sort of.
Also inside the function, there is a variable, users, that stores the results of known inputs and values, that is like the cache(storage) for remembering the stored values.
The returned function takes a parameter name, and it uses a conditional to check if the name parameter is already stored in the cache, and if it is, it returns a different message and also updates the message in the storage accordingly, so that on future calls, it will return the newly stored message, and if the first condition fails, then the else block will make a new message for the user and store that in the cache(as in the variable users) and the displays the message to the user also.
And when we called the function the first time with dave, we get a welcoming sort of message, and when we called the function again with the same argument dave, we get a more friendly and familiar message instead, and when we do the same thing for a new argument, we get the process happening again.
It is important to note that without memoization used here, it will not be that easy to achieve this functionality, it is possible yes, but not what you want to do, it will require more lines of code and logic to get it to work perfectly, so save yourself some headaches really.
What next?
Well, now that you now know what memozation is and how it can be used and not used, you can take more control of the speed of your applications and apply some nitrospeed to your programs. Aside from this, there are some concepts and keywords that might be new to you, so do well to check them out too, in order to make some sense out of this explanation. I don't want to suggest, just feel free to explore, if you do so, you will find out something I don't know haha, so maybe a quick google search can deliver a plethora of resources to you really.
A few of them maybe:
- Closure
- Functional Programming
- HTTP Caching
Whatever I leave out, also include them for your own good.
Alright that is everything, thank you for reading, see you in the next article.
Happy coding!