Code Clarity vs. Performance: Frustrating Mistakes and Examples in Programming

10 min readNov 15, 2023

Reviewer: “why don’t we rewrite this in a more readable way”
Coder: “Oh but this is more optimized!”

Have you ever been in a coding discussion where one person is all about making the code super fast and another is asking to make it easier to understand? This is a common debate in the programming world. In this article, we’re going to take a closer look at why sometimes it’s better for code to be easy to read, and other times, why its speed is what really matters. I’ll be sharing stories and examples from my own experience — some of them might make you nod in agreement, while others might surprise you. If you’ve ever wondered about the best way to write your code, whether you’re new to coding or have been at it for a while, this is for you. Let’s dive in and untangle these coding knots together!

Also, excuse me for ranting about some [very] common mistakes I see almost everyday and I wish were taught at school to make better programmers!

Let’s start with one of the most common and most obvious:

#original code
def isTrue(var):
if var == True:
return True
else:
return False

myvar = True
return isTrue(myvar)
#simplified code
return myvar

This simplification works because the condition var == True is essentially the same as just var when var is a Boolean value.

Ok I know you might be thinking: who does this anyway? But trust me, I’ve seen this in production and the coming code samples are gonna be more interesting, just giving you an introduction here :P

How Quake III Arena might have changed the world!

Skip this section if you’re not interested in optimization and would just like to browse common mistakes and their solutions :)

//original code with original comments from Quake III Arena Game, obscured function name
float obscured_function_name(float number)
{
long i;
float x2, y;
const float threehalfs = 1.5F;

x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

return y;
}

Can you tell what the above function does? Well that is probably the most famous example of optimization magic and sorcery in computer science! (If not, please let me know your example in the comments ;) )

This function is the inverse square root “hack” used in Quake III Arena, the popular 3D video game.

In Quake III Arena, a popular 3D video game, the inverse square root function is used to make calculations related to graphics rendering and physics simulations more efficient. Specifically, it’s often used in normalizing vectors. Normalizing a vector means adjusting its length to 1 while keeping its direction the same. This is important for many calculations in 3D graphics.

The famous part about Quake III’s inverse square root function is how it was implemented. The game used a very clever and efficient method to compute this value quickly. The method involved:

  • Using a special constant (0x5f3759df).
  • Performing some bitwise operations (which are like super-fast math tricks using the binary representation of numbers).
  • Applying a bit of calculus (Newton’s method) to refine the result.

This combination allowed the game to calculate the inverse square root very quickly and with reasonable accuracy. This was crucial because, in the era when Quake III was released, processors were much slower than they are today, so efficiency in calculations had a big impact on the game’s performance.

Here’s a bit more detail:

  1. Floating-Point Representation: Computers represent numbers using a format called floating-point. This format allows for the representation of very large or very small numbers, but it also means that numbers are stored in a way that can be cleverly manipulated.
  2. Bit-Level Hack: The method used in Quake III involved directly manipulating the bits of the floating-point representation of a number. This wasn’t taking advantage of a bug or a limitation, but rather it was using an ingenious understanding of how numbers are stored in a computer.
  3. Leveraging the CPU Architecture: The trick was particularly effective because it reduced the need for time-consuming operations like division. At that time, CPUs were less powerful than today, and operations like finding square roots were relatively slow. By using this method, Quake III was able to perform these calculations much faster, leading to better performance.
  4. Approximation and Refinement: The method provided an approximation of the inverse square root. It wasn’t perfectly accurate, but it was close enough for the needs of a fast-paced game. The algorithm then used a technique from calculus (Newton’s method) to refine this approximation to a more accurate value, which was still faster than traditional methods.

For more details: Fast inverse square root — Wikipedia

To optimize or not to optimize!

Which brings us to the question: “is it more important to optimize and sacrifice readability, or to write more readable code and sacrifice performance?”

Unfortunately, apparently, the common answer in web development nowadays is to prioritize development speed, not readability nor optimization, as many “simple” projects include gigabytes of external libraries just to use simple functions…

Did you know that in the most famous JS library on NPM to check for even numbers, the whole library does nothing except refer to another library and inverse the result?

Here’s the code:

/*!
* is-even <https://github.com/jonschlinkert/is-even>
*
* Copyright (c) 2015, 2017, Jon Schlinkert.
* Released under the MIT License.
*/

'use strict';

var isOdd = require('is-odd');

module.exports = function isEven(i) {
return !isOdd(i);
};

To add insult to injury, did you know that in most cases, no library is even needed and the two libraries can be replaced with a one liner?

//true if even, false if odd
return number % 2 === 0

I say most cases because sometimes we need to check if the number is actually a number (it may be text) or if the number is larger than JS can handle by default, etc in special measures…

Which leads us to the question: is this simply because more junior developers are being handed more difficult tasks, or is it because performance doesn’t really matter that much?

It depends…

I know some readers may be laughing out loud now because they have seen many similar situations in production… While the remaining audience may have already stopped reading this “silly article”, I mean who doesn’t know these things already right? Wrong, you’re in for a surprise!

Let’s look at the following example:

#code approach 1
data = "Hello, World!"
print(''.join([chr(ord(c) + 1) for c in data if c.isalpha()]))
#code approach 2
data = "Hello, World!"
result = ""
for c in data:
if c.isalpha():
result += chr(ord(c) + 1)
print(result)

Which approach do you think is better?

Analysis

  • Optimized Code: The first block uses Python’s powerful one-liners to perform the task in a compact manner. However, this compactness can obscure the intent of the code, especially for those not familiar with Python’s advanced features.
  • Simple Code: The second block uses a straightforward, step-by-step approach. It’s longer and perhaps less elegant but is far easier to read, understand, and maintain.

In this example, the optimization does not significantly improve performance. Instead, it changes the style of the code to be more concise, which can be less accessible to beginners or those not familiar with Python’s idiomatic practices. This illustrates how, sometimes, “optimizing” code by making it more concise or using advanced language features doesn’t necessarily add value in terms of performance, and can even detract from readability and maintainability.

Embedded Systems & Profiling

In the world of embedded systems for example, performance is almost always prioritized over readability, where comments can fill in the gaps and describe what’s going on behind the scene.

So, the answer becomes: What real-world difference is this optimization going to make?

And here comes the power of “profiling”, however, I’ll leave that topic for another post.

Common Programming Mistakes [A Rant]:

  1. Guard Clause
    A guard clause is simply a check that results in immediately exiting the function, either with a return or an exception, rather than continuing with the function unnecessarily.
#without a guard clause
found_items = 0
for item in items:
if is_found(item):
found_items += 1

if found_items > 0:
# Do something based on the found items
do_something()

# ... rest of the code
#With a guard clause
for item in items:
if is_found(item):
do_something()
break # Exit the loop as soon as an item is found

# ... rest of the code

A guard clause is also used to improve readability by handling edge cases or preconditions at the start of a function, thereby reducing the need for deeply nested if-else structures.

#Without guard clause
def process_data(data):
if data:
# Complex processing logic
processed_data = "Processed " + data
# More logic
return processed_data
else:
return "No data provided"
#With a guard clause
def process_data(data):
if not data:
return "No data provided"

# Complex processing logic
processed_data = "Processed " + data
# More logic
return processed_data

2. Query in loop

// Pseudocode for querying inside a loop
for (let id of idList) {
let result = database.query(`SELECT * FROM table WHERE id = ${id}`);
processResult(result);
}
// Pseudocode for a bulk query
let query = `SELECT * FROM table WHERE id IN (${idList.join(",")})`;
let results = database.query(query);
for (let result of results) {
processResult(result);
}

In this optimized version, only one query is sent to the database, asking for all rows where the id is in the idList. This approach is significantly more efficient, especially when dealing with large datasets.

However, when using string concatenation for queries, it is crucial to be aware of the risk of SQL injection. In real-world applications, always use parameterized queries or prepared statements to mitigate this risk.

// Pseudocode for a safe bulk query
let placeholders = idList.map(() => '?').join(',');
let query = `SELECT * FROM table WHERE id IN (${placeholders})`;
let results = database.query(query, idList);
for (let result of results) {
processResult(result);
}

3.set vslist

def find_common_element(array1, array2):
for element1 in array1:
for element2 in array2:
if element1 == element2:
return True # Return True as soon as a common element is found
return False # Return False if no common elements are found

# Example usage
array1 = [1, 2, 3, 4, 5]
array2 = [6, 7, 8, 9, 5]
print(find_common_element(array1, array2)) # Output: True, since 5 is common

This approach is straightforward but not the most efficient, especially for large arrays, due to its O(n*m) complexity, where n and m are the lengths of array1 and array2, respectively.

def find_common_element(array1, array2):
set1 = set(array1)
set2 = set(array2)
#to find the common elements use:
#set1.intersection(set2)
return not set1.isdisjoint(set2)

# Example usage
array1 = [1, 2, 3, 4, 5]
array2 = [6, 7, 8, 9, 5]
print(find_common_element(array1, array2)) # Output: True, since 5 is common
  • Convert both arrays to sets. This has a time complexity of O(n) for each array.
  • Use the isdisjoint method to check if the two sets have any elements in common. This method returns True if two sets have no elements in common and False otherwise. The complexity of isdisjoint is generally O(min(len(set1), len(set2))), which is more efficient than a nested loop.

4. Using built-in functions

# Using a loop to compute the sum of a list
def sum_list(lst):
result = 0
for x in lst:
result += x
return result
# Using the built-in sum function
def sum_list(lst):
return sum(lst)

5. Using generator expressions

#Using a for loop to create a generator
def even_numbers(n):
result = []
for i in range(n):
if i % 2 == 0:
result.append(i)
return result
# Using a generator expression to create a generator
def even_numbers(n):
return (i for i in range(n) if i % 2 == 0)

6. Preallocating memory

# Without preallocating memory
def compute_sums(n):
result = []
for i in range(n):
result.append(sum(range(i)))
return result
# With preallocating memory
def compute_sums(n):
result = [0] * n
for i in range(n):
result[i] = sum(range)

Preallocating memory for lists or arrays can improve the performance of a program by avoiding the overhead of repeatedly resizing the data structure.

7. Memoization

# Without memoization
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
# With memoization
def fibonacci(n, memo={}):
if n in memo:
return memo[n]
if n == 0:
return 0
elif n == 1:
return 1
else:
result = fibonacci(n-1, memo) + fibonacci(n-2, memo)
memo[n] = result
return result

Memoization is a technique that stores the results of expensive function calls so that they can be reused later without having to be recomputed. This can be especially effective for recursive functions.

8. Indexed Arrays vs Associatively Indexed Arrays

//don't do this
const UserTypes = [
{ key: 0, value: 'SuperAdmin' },
{ key: 1, value: 'User' },
{ key: 2, value: 'Editor' },
{ key: 3, value: 'Gatherer' },
];

const StatusTypes = [
{ key: 0, value: 'draft' },
{ key: 1, value: 'pending' },
{ key: 2, value: 'completed' },
];

const FormStatusTypes = [
{ key: 0, value: 'draft' },
{ key: 1, value: 'submitted' },
];

module.exports = {
UserTypes,
StatusTypes,
FormStatusTypes,
}

Why simulate an indexed array in a less efficient manner?

//indexed arrays
const UserTypes = ['SuperAdmin', 'Admin', 'Editor', 'User'];

const StatusTypes = ['draft', 'pending', 'completed'];

const FormStatusTypes = ['draft', 'submitted'];

module.exports = {
UserTypes,
StatusTypes,
FormStatusTypes,
};
//as objects (simulating enums)
const UserTypes = {
SuperAdmin: 0,
Admin: 1,
Editor: 2,
User: 3
};

const StatusTypes = {
Draft: 0,
Pending: 1,
Completed: 2
};

const FormStatusTypes = {
Draft: 0,
Submitted: 1
};

module.exports = {
UserTypes,
StatusTypes,
FormStatusTypes
};

I hope that you found this helpful! Do let me know in the comments if I have missed any other common mistakes!

--

--

Steve Mostafa Dafer
Steve Mostafa Dafer

Written by Steve Mostafa Dafer

Tech visionary, speaker, mentor, tinkerer, teacher, and engineer! https://www.linkedin.com/in/stevedafer/

Responses (1)