Casually Making Your Programs Faster: Redefining Loops

Chahat Gupta
4 min readDec 31, 2023

--

Be it for front-end or backend tech, we use loops all the time. Loops are where the actual programming begins — from brute-force sorting to repeated calculations. In this blog I’ll be sharing what I have learnt about loops, and how to make them faster.

Observation

Taking a for loop as a reference, let’s see what actually can be optimised.

int[] numbers = {1, 2, 3, 4, 5};

for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}

Let’s break down the four main parts of the above loop, along with their possible optimisation scope:

  1. Initialisation: Happens once, no optimisation scope 🟨 ⬜️ ⬜️ ⬜️ ⬜️
  2. Condition check: Has a lot of room for optimisation 🟨 🟨 🟨 🟨 ⬜️
  3. Execution: Depends on use case but can be optimised 🟨 🟨 ⬜️ ⬜️ ⬜️
  4. Increment/Decrement: Has some scope of optimisation 🟨 🟨 ⬜️ ⬜️ ⬜️

Optimisation Methods

1 — Caching condition checks (and whatever is possible)

The quickest optimisation you can make in most cases is to cache the test condition check in the loop.

int[] numbers = {1, 2, 3, 4, 5};

// caching the value of numbers.length
int length = numbers.length;

for (int i = 0; i < length; i++) {
System.out.println(numbers[i]);
}

Storing the length value outside the loop ensures that numbers.length is called only once. The same applies to ArrayList.size() or any data structure in any other language. If you’re solving LeetCode problems this move alone will save you some extra milliseconds.

2 — Make calculations simpler with alternate approaches

Take a look at this simple loop aiming to print all divisors of the integer num .

int num = 36;

for (int i = 1; i <= Math.sqrt(num); i++) {

if (num % i == 0) {
System.out.println(i);

if ((num / i) != i) {
System.out.println(num / i);
}
}
}

From our last learning we know that Math.sqrt() will be called several times. This might be an expensive operation depending on the value of num . Moreover, depending on your language and framework the time complexity of this simple iteration can range from O(1) to O(log n) .

To simplify this, we can use an alternate calculation i * i <= num . This will give the exact same result, strip off the floating point intervention and make the program much faster. The amended program will look like this.

int num = 36;

for (int i = 1; i * i <= num; i++) {

if (num % i == 0) {
System.out.println(i);

if ((num / i) != i) {
System.out.println(num / i);
}
}
}

To add a cherry on the cake, you can combine it with the caching approach.

3 — Loop Unrolling

Loop unrolling is a technique to optimise program execution time by reducing iterations. It speeds up the program by eliminating loop control and test instructions and replicating or unfolding the loop body to handle multiple iterations in a single pass. Let’s understand this with a simple program.

int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = 0;

int length = numbers.length;

// Unrolled loop by processing two iterations at a time
for (int i = 0; i < length - 1; i += 2) {
sum += numbers[i];
sum += numbers[i + 1];
}

// Handling any remaining element if the array length is odd
if (length % 2 != 0) {
sum += numbers[length - 1];
}

System.out.println("Sum (Unrolled Loop): " + sum);

⚠️ Warning: Loop unrolling may involve manual control over the indexes, mishandling may lead to crashes. It’s good to test the program with different inputs before using this approach in production.

4 — Using bitwise operations wherever possible

People don’t like bitwise operations until they learn it. Bitwise operations are generally much faster than arithmetic operations and can save you time as well as space inside loops.

I’m listing a handful of operations commonly used in loops that can be optimised using bitwise operations:

// [1] Using left shift operator with 1 multiplies by 2

int b = a * 2; ❎
int b = a << 1; ✅


// [2] Bitwise AND with 1 checks for even numbers

if (num % 2 == 0) { ❎
// even
} else {
// odd
}

if ((num & 1) == 0) { ✅
// even
} else {
// odd
}


// [3] Swap using bitwise XOR

int temp = a; ❎
a = b;
b = temp;

a = a ^ b; ✅
b = a ^ b;
a = a ^ b;

The use of bitwise / bit shift operators put that extra 2% into your program and make you stand out. I will be writing separately about bitwise operators in a subsequent blog. You can learn more about these here.

Conclusion

Loops are everywhere! Be it in the backend or frontend, they come in different types to serve various purposes. What’s important to understand is that they empower a program with controlled repetitions, a capability that comes with great responsibility.

In this blog, I have discussed some of the many ways to optimise loops, which I use in my day-to-day coding. If you know of any more, let’s discuss them in the comments.

🚀 Let’s connect!

Connect with me on LinkedIn for professional insights and networking.

Explore my contributions on GitHub and Stack Overflow to collaborate on something amazing! ✨

Consider clapping 👏🏼 if you found this article insightful.

just another meme for the feature image

--

--

Chahat Gupta
Chahat Gupta

Written by Chahat Gupta

Mobile Tech Lead specialising in Android, iOS, and Flutter. Sharing insights and learnings on mobile development to inspire and elevate tech professionals.

No responses yet