🎯 Context
This text is aimed to provide an alternative to these situations where our code must be structured in order to run one or another function, depending on a bunch of possible conditions.
I don't want to criticize the use of if/else
or switch/case
sentences far from it. My only goal is to provide a different approach which can improve the maintenance and scalability of our code.
So... engage!!!
📚 if/else sentences
Since all of us start learning to code, the first flow control sentence that we learn is the if/else
one (MDN if/else documentation). So once we have got it, it's really easy to use.
Even when the amount of possible options increases, we can chain multiple if/else
.
In addition, when we have several options which must be treated in the same way (it means with the same business logic), we can use boolean operators (the OR
one in this case), in order to group all these options under the same block.
That's fine but when the possible cases are upper than two or three options, the code starts to look like a little bit messy.
Pros (✅) and cons (👎)
✅ It's the easier way to control data flow.
✅ It's relatively easy to learn.
✅ It's really comfortable to discriminate between two options.
👎 For handling more than three options, the code starts to look like a little bit messy.
👎 To chain multiple options reduce the readability and maintenance of our code.
👎 To group options using boolean operators could turn on complex assertion rules for every situation.
👎 For a relatively huge amount of cases, it's slow due to every condition must be checked until getting the only one that matches.
🤓 switch/case sentence
When we want to improve the readability and maintenance of our code due to we have multiple options to be handled, is when we learn the if/else
alternative, it means, the switch/case
sentence (MDN switch/case documentation).
In the same way we did with the if/else
sentence, with switch/case
we can also group options but now, we don't need to use any boolean operator. We just need to keep joined the different cases.
As you already know, that's possible due to the switch/case
execution is a sequential process, where every possible case defined in the block of options, is compared with the provided value.
If both values match, the code block included into that case is executed and, if there is not a break
or return
instruction at the end of the code block, the next case will be checked until the next matching or the default
option is reached.
Based on that, in order to group several options which are going to be handled by the same code block, we just need to define the case
for the wished value, with no business logic. This way we're able to chain multiple options for the same result.
Pros (✅) and cons (👎)
✅ It provides a better code structuring than if/else
sentences.
✅ It's possible to create clearest cases grouping than if/else
sentences.
✅ It's really easy to discriminate between more than two options.
👎 You have to be aware about completing all your code blocks with a break
or a return
instruction. If you forget it, you'll have a real nightmare.
👎 For a relatively huge amount of cases, it's slow due to every condition must be checked until getting the only one that matches.
🔥 Mapped functions
This is a little known strategy (a.k.a. object lookup
) aimed to improve several aspects of if/else
and switch/case
sentences.
The idea is to take advantage of a JavaScript object behavior in order to use its keys as map references to access to specific business logic.
First at all, we need to have defined the possible cases which must be handled.
Every single case will be bound to a key in the literal object.
Once we have created our object, we'll use array-access style to run the code for every single case.
Pros (✅) and cons (👎)
✅ It provides a better code structuring than if/else
and switch/case
sentences.
✅ There is no cases grouping due to every single case has its own business logic.
✅ It's extremely easy to differentiate between multiple options.
✅ Can be reused in several parts of our application (via module export).
✅ It's faster than if/else
and switch/case
due to we access to the specific condition without need to check every defined case sequentially until the correct one is located.
👎 This strategy rarely appears in common trainings.
👎 If the object is not defined in the right place, it can consume a little bit more memory that it really needs.
🤔 FAQ
❓ What happen if the provided option is not defined as object key?
The short answer is that an exception will be throw due to it's not possible to run a function from undefined
.
However, we can prevent it defining a default
case, in the same way we do in switch/case
sentences.
In order to be able to access this new case, we will check if the provided one already exists into the object and if it doesn't, we run the default
option.
For these cases, the conditional (ternary) operator will be our allied.
❓ What can/must I return in the default
case?
It'll depend on the use case we are defining but basically, we have three main options:
1 - To return the same value that you have provided:
2 - To return null
or undefined
:
In this case, we can even take advantage of the optional chaining and clean up the code this way:
We have to pay attention because in this last case, if there are no matching options, we are going to be returning undefined
.
3 - To define a specific business logic:
We must be careful if our code, like in this example, is going to throw an error. We need to handle it in order to avoid a full blocking error.
Obviously the code that implements the error can be replaced for any other business logic which suites better with our application behavior.
❓ Do I need to define an anonymous function for every case?
No, you don't.
If we have perfectly defined the function that must be run for every case and in addition, that function receives only a single argument which matches with the provided one when you invoke the map, we can use this syntax:
Even if we want to return undefined
when the provided option is not included into the map, we can use this extremely simplified syntax (Caveat ‼️: the whole functions used in order to create the mapped object keys must be defined previously):
❓ It could be possible that property name clashes with an object one?
Absolutely yes.
It's possible at all, but in order to avoid that we have to pay attention what names are we using, in the same way we never use a language reserved word as variable, function or object name.
❓ It could force a naming convention?
Yes, it could.
But for these situation we have the support and guidances of Clean Code.
Every code we create requires naming convention. Some cases when we are the only person who has started the project, we can define that convention (pet-projects mainly). In other situations, the development team will be the responsible of any agreement about that.
❓ Is it going to require additional memory meanwhile if/else
and switch/case
does not do it?
Yes, it will.
However, based on the kind of devices that run our JavaScript applications nowadays and its characteristics, the increment of used memory is insignificant compared with the rest of the application.
❓ Should it be slower that if/else
or switch/case
depending on the JavaScript engine?
It depends on where we define the object.
For instance, if we define the mapped functions object into a function which is going to be called every time we want to use the map, obviously this solution is going to be slower that the other options because the object must be created every time.
In this code we can see that situation where the mappedFunction
has defined the object lookup inside:
Codepen 👉 Speed race Switch 🐇 vs Object Lookup 🐢 v1
It doesn't matter which JavaScript engine you use to run this code (AppleWebKit for Safari, SpiderMonkey for Firefox or V8 for Google Chrome and/or NodeJS), because the mapped function will be always slower (even with the first cases) due to the object is created ad-hoc for every function run.
Nevertheless, if we define the mapped functions globally (to the module or to the application), the object will be loaded just once when the module or the application is used so, this way, the mapped function access is always faster than the other two options.
In this another code we have defined the object lookup outside the mappedFunction
:
Codepen 👉 Speed race Switch 🐢 vs Object Lookup 🐇 v2
❓ What about the garbage collector?
Nowadays the garbage collector is something that the JavaScript developers don't pay too much attention due to it's widely covered by the language specifications so, once the mapped functions object is not used any more by the current runtime, it will be managed by the garbage collector automatically.
For further information about that, I recommend you to take a look to this documentation of the MDN about memory management.
Additional resources:
👋 Final words
As I said at the beginning of this post, it's not my intention to criticize the use of if/else
or switch/case
, but I only want to provide another way to do these operations.
Summarizing, when we've to discriminate between two single options, it's obvious that the easier alternative is to use if/else
sentence. Moreover I strongly recommend you to try to use the ternary operator when it's possible.
For cases where you have to differentiate between three or more options, I sincerely recommend you to use mapped functions in order to provide a better readability, maintenance and code reuse.
I hope this tip is useful for you. If you have any question, feel free to contact me. Here there are my Twitter, Linkedin and Github profiles.
🙏 Credits and thanks
- Lissette Luis, Adrián Ferrera and Iván Bacallado for being part of an awesome knowledge sharing team and specially, for their proposals about the subject covered on this post.
- Simon Høiberg for starting this interesting Twitter thread that originated the creation of this post.
- Kenan Yildiz and Thomas Luzat for sharing a shorter way to implement the object lookup.
Top comments (6)
I have a use case that requires fetching ~400 name=value pairs of lat lon geocordinates from the U.S. Census Bureau API. I need to store the values to be accessed on an as-needed basis such that one pair of geocordinates can be retrieved and used as keys submitted to a weather API.
Would Object lookup be suitable for this?
If I've understood right, you want to use the pair lat-lon as key in order to apply the lookup procedure.
Thinking that's correct, the process shown in this post is not the most accurate for that because you are going to create the key as string so every time you want to find any specific location, your code has to parse the lat-lon object to string and then, searching for it in the mapped object. I think that "it's not the way" ;)
In opposite, I propose you use to give a try to Map object (MDN documentation) which provides the option to use plain objects as keys and looking for using that objects. That should provide you flexibility and automation advantages.
🤫 NOTE: the second part of this post where I cover the use of Map and TypeScript in order to keep handling the object lookup, will be published in the coming days 😉
In the other way, if you are worried about the memory management, you can use Weakmap. This post is really interesting: Beyond the basics object vs map by Matthew Aftalion.
I hope my answer has provided some light about your doubt.
Thank you for some insight into this context...
Thanks this is interesting, I don't know why but when I look at this I think of pattern matching, does JS support pattern matching?
It doesn't exist like that right now. However it's proposed to the tc39 and this proposal is currently in Stage 1.
github.com/tc39/proposal-pattern-m...
Apparently there are several tries to implement it but I think they are simple patches until it's completely adopted.
bramstein.com/writing/pattern-matc...
Thanks for looking into it :).