Optimizing the hell out of JS Code

Optimizing the hell out of JS Code

In this post, I want to share a few of the learnings I made (over)optimizing the hell out of a tiny JavaScript app.

Background

The basketball league, my son is playing in, doesn't share a ranking table to not make the kids too concerned about their results. As a curious dad and engineer, I did the only reasonable thing and created a little script myself, which generates the table based on the publically available game results.

After an hour of coding, I had the initial version done. Just simple stuff: fetching, processing, and displaying data:
Initial v0.1 - Code - Live Application

Over the next few weeks, for some reason I kept applying tiny improvements to the code, which completely escalated:
Final v1.0 - Code - Live Application

Just looking at some hard numbers it's clear that the initial version has little to do with the latest one:

JS keywordInitial v0.1Final v1.0
const/var■■■■■■■■■■■■■■■ 15 1
() => () 1■■■■■■■■ 8
function■■■■■ 5■■■■■ 5
return■■■■■■■ 7■■ 2
for■■ 20
if■■■■■■■ 70
Total lines of JS code10951

So what happened...

Learning I: Functional programming

As the numbers already tell, the biggest difference between the two versions is of course how much more functional the coding became. I did not intend to go in this direction, but it just felt very natural for data processing to pipe results from function to function, chain calls to array methods, remove temporary variables, etc.
Clearly, this coding style takes some time to get used to and might not be for everyone, but without a doubt, it’s much more succinct:

// INITIAL v0.1
for (const match of team.matches) {
  if (match && match[0] && match[0].result) {
    var [homeResult, guestResult] = match[0].result.split(':');
    homeResult = parseInt(homeResult);
    guestResult = parseInt(guestResult);

---

// FINAL v1.0
team.matches
  .flat()
  .filter(match => match.result)
  .filter(match =>
    [team.homeResult, team.guestResult] = match.result.split(":").map(Number);

---

/* provided DATA STRUCTURE
team : {
  matches : [
    [{result : "100:50", ..}],
    [{result : null, ..}],         // past game canceled or postponed
    [{result : "20:30", ..}],
    [],                            // idk  ¯\_(ツ)_/¯ 
    ..
  ]
}

Learn more: Simplify your JavaScript – Use .map(), .reduce(), and .filter()

Learning II: Promises to prettify the flow

I’m sure you all know what Promises are good for (and not) and why asynchronous development is awesome, but did you know that they are also a wonderful tool to make the general application flow obvious:


// INITIAL v0.1
const app = async () => {
  var dataUrl = "...";
  var data = await loadData(dataUrl);
  var table = process(data).sort(sortTable).reverse();
  renderTable(table);
}
app();

---

// FINAL v1.0
var dataUrl = "...";
proxyLoadData(dataUrl)
    .then(processData)
    .then(outputData)
    .catch(handleError);

Learning III: ♥ for JavaScript magic

JavaScript is full of little tricks and hacks to get things done quicker. From years of PHP and Perl development, I love all this ?? , ?., !!, ~~, ... craziness 😆

Learn more: 25 Useful JavaScript Shorthands for Web Developers

Learning IV: Fetch Caching instead of LocalStorage

I wrote an extra article about an interesting solution to avoid using LocalStorage to cache API responses:

Learning V: Ask for forgiveness, not permission

As always a great way of simplification is to just do stuff, instead of checking if it needs to be done, only avoiding wrong side effects. As an example just check the function processData, where these two lines add the correct number of played games and wins no matter if the game happened, was canceled or postponed, lost or won:

team.games += Number(Boolean(match.result));
team.wins += Number(team.gamePoints > team.gamePointsOpponent);

Learning VI: Simplicity

It was a fun experience just playing around with a single .html file. I almost forgot how easy things can be with no build process, no configs, no libs, no deployment, and no IDE. Just plain code and super quick Trial and Error, which is still the best teacher in the world.

Conclusion

So first of all, without a doubt I overdid it. I spend way more time on it than would be reasonable for any real-world project. I sacrificed some readability just to show off that I can even save a few more lines, which is the opposite of what is regarded as good software engineering. (see this wonderful example and discussion on Twitter). But overall, I loved the process and learned, or at least confirmed quite a few things.

In any case, feel free to let me know what you think, and for sure reach out in case you see further simplifications.