The Art of Optimization

There are a couple of ways to view code optimization. Traditionally, we would focus on speed and space, but as space becomes cheap and computing power easily available, the focus of optimization now shifts to developer hours.

This is a good thing, the faster we can ship our product the faster people can access it. Sometimes its even nessecery because of the competition.

But this got me to question - When exactly should real performance matter? Is optimizing for speed and space obsolete? And should we all stop using the array functions?

Let's explore this through a cod wars excersice: checking if a string is an isogram (has no repeating letters).

Version 1: The Elegant Solution

We start with a clean, readable solution, (Top Solution for this problem) its clever that uses modern JavaScript features:

function isIsogram(str) {
return new Set(str.toLowerCase()).size === str.length;
}

This code is beautiful. It's self-documenting and uses built-in JavaScript features. One line tells the whole story: convert to set of unique characters, compare the set to string length.

Thus if original string was "asdfa" the set would consist of [a,s,d,f] and its length 4 would not be equal to the original string length of 5.

Version 2: The Classical Approach

Then we tried a more traditional nested loop approach:

function isIsogram(str){
const arr = str.toLowerCase().split('');
const length = arr.length;
for(let i = 0; i < length ; i++){
for(let j = i + 1; j < length; j++) {
if (arr[i] === arr[j]) return false;
}
}
return true;
}

Less elegant, but surprisingly performant in many cases due to early termination and CPU-friendly sequential access patterns.

In here we keep track of two pointers to array - i and j. We "take out" the first item of the array, and move through all others compering them.

In case of abba we would start with 'a' and go over 'bba' comparing each letter to the one we took out ('a'). When we find our match at the end of the word we return false. Otherwise we would do the same with the rest of the word 'bba' taking out 'b' and leaving us with 'ba' to check.

Version 3: The Bit Vector Solution

Finally, we arrived at a low-level optimization using bit manipulation:

function isIsogram(str) {
let chars = 0;
for(let i = 0; i < str.length; i++) {
const bit = 1 << ((str.charCodeAt(i) | 32) - 97);
if (chars & bit) return false;
chars |= bit;
}
return true;
}

This version treats each letter as a bit in a number, using binary operations for ultra-fast checking. It's like having 26 light switches that we flip on as we see each letter.

Notice how each time, by removing syntactic sugar we made our code go faster. This is true for loops, and arrays in general. Even going as far as arr.foreach being slower than the traditional for(let i = 0; i < num; i++)!

Performance Comparison

The test runs 100,000 randomly generated strings through each of the functions. String length varies from 5 to 500 even thou after 26 (chars in alphabet) this should only benefit the bit function.

🔑 Key Takeaways

  • Readability vs Performance: The Set version is the most readable and maintainable, while the bit vector version is the most optimized but harder to understand.
  • Context Matters: For small strings or infrequent operations, the performance difference is negligible.
  • Modern Features aren't Always Slower: The Set version performs reasonably well for many use cases.
  • Optimization is a Journey: Each version taught us something about both the problem and JavaScript's behavior.

So, When Should You Optimize?

Before diving into optimization, ask yourself:

  • Is this code in a critical path?
  • How often will it run?
  • What's the typical input size?
  • Is the performance impact measurable?
  • Does the optimization make the code significantly harder to maintain?

Remember Donald Knuth's famous words: "Premature optimization is the root of all evil." Start with clean, maintainable code. Optimize only when you have evidence that it's necessary.


Leave a Reply

Your email address will not be published. Required fields are marked *