From if/switch to mapped functions (a.k.a. object lookup)
Tags: javascript, node, webdev
Published: 2020-12-14 13:53:43

[🇪🇸 Versión en español](https://dev.to/ddialar/de-if-switch-a-mapped-functions-a-k-a-object-lookup-1pb8)

## 🎯 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else)). So once we have got it, it's really easy to use.

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=simpleIfElse1.js %}

Even when the amount of possible options increases, we can chain multiple `if/else`.

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=simpleIfElse2.js %}

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.

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=groupedIfElse1.js %}

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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch)).

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=simpleSwitchCase1.js %}

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.

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=groupedSwitchCase1.js %}

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.

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=mappedFunction1.js %}

#### 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) will be our allied.

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=mappedFunction2.js %}

#### ❓ 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:

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=mappedFunction3.js %}

2 - To return `null` or `undefined`:

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=mappedFunction4.js %}

In this case, we can even take advantage of the [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) and clean up the code this way:

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=mappedFunction5.js %}

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:

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=mappedFunction6.js %}

**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:

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=mappedFunction7.js %}

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):

{% gist https://gist.github.com/ddialar/6f88d452a5e332dd9d855df865e00c4f file=mappedFunction8.js %}

#### ❓ 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](https://codepen.io/ddialar/pen/JjRWNBJ)

It doesn't matter which JavaScript engine you use to run this code ([AppleWebKit](https://en.wikipedia.org/wiki/WebKit#JavaScriptCore) for Safari, [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) for Firefox or [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) 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](https://codepen.io/ddialar/pen/ZEpeKqW)

#### ❓ 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management).

Additional resources:
- [Garbage collection by javascript.info (2020)](https://javascript.info/garbage-collection)

## 👋 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](https://twitter.com/ddialar), [Linkedin](https://linkedin.com/io/ddialar) and [Github](https://github.com/ddialar) profiles.

## 🙏 Credits and thanks

- [Lissette Luis](https://twitter.com/LissetteIbnz), [Adrián Ferrera](https://twitter.com/AdrianFerrera91) and [Iván Bacallado](https://twitter.com/ivanbtrujillo) for being part of an awesome knowledge sharing team and specially, for their proposals about the subject covered on this post.
- [Simon Høiberg](https://twitter.com/SimonHoiberg) for starting this interesting [Twitter thread](https://twitter.com/SimonHoiberg/status/1337688868527726593?s=20) that originated the creation of this post.
- [Kenan Yildiz](https://twitter.com/knnyldz90) and [Thomas Luzat](https://twitter.com/tluzat) for sharing a shorter way to implement the object lookup.