1. Introduction
In this tutorial, we’ll explain the difference between the pre-increment and post-increment operators in a loop.
2. Pre-increment and Post-increment
Many programming languages such as Java or C offer specialized unary operators for the pre-increment and post-increment operations: () and (). Both increment their argument variable by 1, but not the same way.
In particular, if x is an integer variable, and we pre-increment it as a part of a larger expression, the program will first add 1 to x and then evaluate x to the new value as the part of the evaluation of the entire expression. For example:
int x = 4;
int y = (++x) + 100;
// x = 5, y = 105
In contrast, the post-increment alternative will increment x but evaluate it to the old value inside the expression. Only after the expression has been evaluated does x have the new value:
int x = 4;
int y = (x++) + 100;
// x = 5, y = 104
As we can see, x=5 in both cases, but the final value of y is different.
3. Pre-increment and Post-increment in a Loop
Let’s analyze how to use these operations inside a loop. We’ll focus on the loops with increasing counters.
3.1. The General Form of a Loop
Each counter-based loop is specified by:
- its counter’s initial value
- the termination condition
- the increment step
- and the loop’s body
Schematically:
algorithm GenericLoopForm():
// This generic form demonstrates the structure of a loop
counter <- initialize_the_counter()
while termination condition is false:
body()
increment(counter)
If the termination test and the increment step are executed separately, it doesn’t matter which operator we use to increase the counter. The results will be the same.
However, if the termination condition test includes the increment operation, the two alternatives can lead to different results.
3.2. Testing the Condition With the Post-Increment
Let’s say that we want to print all the numbers between 1 and , inclusive. With the post-increment operator, we can try something like this:
algorithm PrintNumbersPostIncrement():
// This algorithm prints numbers from 1 to 10
// using post-increment in the condition
i <- 0
while i++ <= 10:
print(i)
How does this loop unfold?
First, we set to 0. Then, we check the condition by testing if and updating to right after. Following the update, we execute the loop’s body.
Then, we repeat the iteration. We test the condition . Since , we set to and move on to the print statement.
We go on like this until right before the test . Since we’re working with the post-increment operator, we use the current value of to check the condition and then add 1 to it. So, since before the check, and , passes the test, which means we’ll execute the loop’s body. Because we use the updated counter after the test, we print . Then, we go back to the condition . Since , we stop the loop.
The problem is that we’re printing even though we wanted only the numbers .
Therefore, we need to change the condition to or . Unfortunately, that’s a bit counter-intuitive. If we test with or , it isn’t clear straight away that we want to include the number 10 in the loop. The condition with would make more sense.
3.3. Testing the Condition With the Pre-Increment
Let’s rewrite the loop to use the pre-increment operator. For instance:
algorithm PrintNumbersPreIncrement():
// This algorithm prints numbers from 1 to 10
// using pre-increment in the condition
i <- 0
while ++i <= 10:
print(i)
Will this loop include ? Well, since we first increment and then check if its new value is , the last number we print will be . In the very next iteration, evaluates to , which is false, so we end the loop.
3.4. Readability
Coupling the increment steps with the termination checks is a bad idea because the code gets less readable. Even though we made both loops work in the previous examples, we still needed to think about the last number and the correct formulation of the stopping criterion.
Also, although correct, the above loops don’t have an intuitive initialization step. Both initialize the counter as . Until thoroughly analyzing the loops, most people would think that they print 0. In contrast, there’s no confusion with a loop with decoupled test and increment steps:
algorithm PrintNumbersWithSeparatedTestAndIncrement():
// This algorithm prints numbers from 1 to 10,
// separating the test and increment steps.
i <- 1
while i <= 10:
print(i)
i++ // ++i would also work here
Its meaning is immediately apparent. In general, we should strive for such a level of readability.
3.5. The Increment Step
As we said before, the pre-increment and post-increment operators don’t affect the semantics and correctness of our loop when we use them to increase its counter.
However, some object-oriented languages, such as C++, allow us to implement the operators in our classes. Using such an object as our loop’s counter, we may see that the post-increment alternative runs slower than the pre-increment one.
The reason is that increments but returns the old value. So, we need to store the new value until we evaluate the entire expression that participates in. The loop’s complexity won’t change, but if is a complex object, it can run slower because of copying the content of in each increment step.
4. Conclusion
In this article, we covered the difference between the pre-increment and post-increment operators inside a loop’s termination condition.
As a rule of thumb, we should decouple the increment step from the termination condition test to make our code more readable.