Coming Soon

This lesson is currently being developed

Increment/decrement operators and side effects

Learn prefix and postfix increment/decrement operators.

Operators
Chapter
Beginner
Difficulty
40min
Estimated Time

What to Expect

Comprehensive explanations with practical examples

Interactive coding exercises to practice concepts

Knowledge quiz to test your understanding

Step-by-step guidance for beginners

Development Status

In Progress

Content is being carefully crafted to provide the best learning experience

Preview

Early Preview Content

This content is still being developed and may change before publication.

6.4 — Increment/decrement operators and side effects

In this lesson, you'll learn about the increment (++) and decrement (--) operators, understand the crucial difference between their prefix and postfix forms, and discover how side effects can create subtle bugs in your programs.

What are increment and decrement operators?

The increment operator (++) increases a variable's value by 1, while the decrement operator (--) decreases a variable's value by 1. These operators are shortcuts for the common operations of adding or subtracting 1.

Instead of writing x = x + 1, you can simply write ++x or x++.
Instead of writing y = y - 1, you can simply write --y or y--.

Both operators come in two forms:

  • Prefix (++x, --x): Operator comes before the variable
  • Postfix (x++, x--): Operator comes after the variable

The form you choose affects when the increment/decrement happens and what value is returned.

Basic increment and decrement operations

Simple increment and decrement

#include <iostream>

int main()
{
    int x = 5;
    int y = 10;
    
    std::cout << "Initial values: x = " << x << ", y = " << y << std::endl;
    
    // Increment x by 1
    ++x;  // x becomes 6
    
    // Decrement y by 1
    --y;  // y becomes 9
    
    std::cout << "After increment/decrement: x = " << x << ", y = " << y << std::endl;
    
    // Multiple operations
    ++x;  // x becomes 7
    ++x;  // x becomes 8
    --y;  // y becomes 8
    --y;  // y becomes 7
    
    std::cout << "After more operations: x = " << x << ", y = " << y << std::endl;
    
    return 0;
}

Output:

Initial values: x = 5, y = 10
After increment/decrement: x = 6, y = 9
After more operations: x = 8, y = 7

Equivalency with arithmetic operators

#include <iostream>

int main()
{
    int a = 5, b = 5, c = 5;
    
    // These are all equivalent ways to add 1
    a = a + 1;      // Traditional form
    b += 1;         // Compound assignment
    ++c;            // Prefix increment
    
    std::cout << "All three should be 6:" << std::endl;
    std::cout << "a = " << a << std::endl;
    std::cout << "b = " << b << std::endl;
    std::cout << "c = " << c << std::endl;
    
    int x = 10, y = 10, z = 10;
    
    // These are all equivalent ways to subtract 1
    x = x - 1;      // Traditional form
    y -= 1;         // Compound assignment
    --z;            // Prefix decrement
    
    std::cout << "\nAll three should be 9:" << std::endl;
    std::cout << "x = " << x << std::endl;
    std::cout << "y = " << y << std::endl;
    std::cout << "z = " << z << std::endl;
    
    return 0;
}

Output:

All three should be 6:
a = 6
b = 6
c = 6

All three should be 9:
x = 9
y = 9
z = 9

Prefix vs. postfix: The crucial difference

The key difference between prefix and postfix forms is when the increment/decrement happens and what value is returned:

  • Prefix (++x): Increment first, then return the new value
  • Postfix (x++): Return the current value first, then increment

Demonstrating the difference

#include <iostream>

int main()
{
    // Prefix increment
    int a = 5;
    int prefixResult = ++a;  // a is incremented to 6, then 6 is returned
    
    std::cout << "Prefix increment:" << std::endl;
    std::cout << "a after ++a: " << a << std::endl;                    // 6
    std::cout << "Value returned by ++a: " << prefixResult << std::endl; // 6
    
    // Postfix increment
    int b = 5;
    int postfixResult = b++; // Current value (5) is returned, then b is incremented to 6
    
    std::cout << "\nPostfix increment:" << std::endl;
    std::cout << "b after b++: " << b << std::endl;                      // 6
    std::cout << "Value returned by b++: " << postfixResult << std::endl; // 5
    
    // Same behavior with decrement
    int c = 10;
    int prefixDec = --c;     // c decremented to 9, then 9 returned
    
    int d = 10;
    int postfixDec = d--;    // 10 returned, then d decremented to 9
    
    std::cout << "\nDecrement comparison:" << std::endl;
    std::cout << "c after --c: " << c << ", returned value: " << prefixDec << std::endl;
    std::cout << "d after d--: " << d << ", returned value: " << postfixDec << std::endl;
    
    return 0;
}

Output:

Prefix increment:
a after ++a: 6
Value returned by ++a: 6

Postfix increment:
b after b++: 6
Value returned by b++: 5

Decrement comparison:
c after --c: 9, returned value: 9
d after d--: 9, returned value: 10

Step-by-step comparison

#include <iostream>

int main()
{
    std::cout << "Step-by-step prefix vs postfix comparison:" << std::endl;
    
    // Prefix example
    int x = 5;
    std::cout << "\nPrefix (++x):" << std::endl;
    std::cout << "x before: " << x << std::endl;           // 5
    std::cout << "++x returns: " << (++x) << std::endl;   // 6 (x incremented first)
    std::cout << "x after: " << x << std::endl;           // 6
    
    // Reset and show postfix
    x = 5;
    std::cout << "\nPostfix (x++):" << std::endl;
    std::cout << "x before: " << x << std::endl;          // 5
    std::cout << "x++ returns: " << (x++) << std::endl;  // 5 (old value returned)
    std::cout << "x after: " << x << std::endl;          // 6 (but x was incremented)
    
    return 0;
}

Output:

Step-by-step prefix vs postfix comparison:

Prefix (++x):
x before: 5
++x returns: 6
x after: 6

Postfix (x++):
x before: 5
x++ returns: 5
x after: 6

When the difference matters

In simple statements where you ignore the return value, prefix and postfix behave identically:

When prefix and postfix are equivalent

#include <iostream>

int main()
{
    int a = 5, b = 5;
    
    // When used as standalone statements, both do the same thing
    ++a;    // a becomes 6
    b++;    // b becomes 6
    
    std::cout << "Both should be 6: a = " << a << ", b = " << b << std::endl;
    
    // In simple for loops, both work identically
    std::cout << "Prefix version: ";
    for (int i = 0; i < 5; ++i) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    
    std::cout << "Postfix version: ";
    for (int i = 0; i < 5; i++) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Output:

Both should be 6: a = 6, b = 6
Prefix version: 0 1 2 3 4 
Postfix version: 0 1 2 3 4 

When prefix and postfix behave differently

#include <iostream>

int main()
{
    // Example 1: Assignment with increment
    int x = 5;
    int y = 5;
    
    int prefixAssign = ++x;    // x incremented to 6, then 6 assigned
    int postfixAssign = y++;   // 5 assigned, then y incremented to 6
    
    std::cout << "Assignment with increment:" << std::endl;
    std::cout << "prefixAssign = " << prefixAssign << ", x = " << x << std::endl;     // 6, 6
    std::cout << "postfixAssign = " << postfixAssign << ", y = " << y << std::endl;  // 5, 6
    
    // Example 2: Array indexing
    int array[] = {10, 20, 30, 40, 50};
    int index = 0;
    
    std::cout << "\nArray access with increment:" << std::endl;
    std::cout << "array[index++] = " << array[index++] << std::endl; // Uses index 0, then increments
    std::cout << "Index is now: " << index << std::endl;             // 1
    std::cout << "array[++index] = " << array[++index] << std::endl; // Increments to 2, then uses index 2
    std::cout << "Index is now: " << index << std::endl;             // 2
    
    // Example 3: Function arguments
    auto printValue = [](int value) {
        std::cout << "Function received: " << value << std::endl;
    };
    
    int counter = 10;
    std::cout << "\nFunction calls with increment:" << std::endl;
    printValue(++counter);  // Increments to 11, passes 11
    std::cout << "counter is now: " << counter << std::endl; // 11
    
    printValue(counter++);  // Passes 11, then increments to 12
    std::cout << "counter is now: " << counter << std::endl; // 12
    
    return 0;
}

Output:

Assignment with increment:
prefixAssign = 6, x = 6
postfixAssign = 5, y = 6

Array access with increment:
array[index++] = 10
Index is now: 1
array[++index] = 30
Index is now: 2

Function calls with increment:
Function received: 11
counter is now: 11
Function received: 11
counter is now: 12

Side effects and sequence points

A side effect is any change to program state that persists after an expression is evaluated. Increment and decrement operations have side effects because they modify variables.

Sequence points are points in program execution where all side effects of previous operations are guaranteed to be complete. In C++, sequence points occur at:

  • The end of a full expression (semicolon)
  • Before function calls
  • At certain operators (&&, ||, ,, ?:)

Undefined behavior with multiple side effects

#include <iostream>

int main()
{
    int x = 5;
    
    // ❌ UNDEFINED BEHAVIOR - multiple modifications between sequence points
    // The result of this is unpredictable and compiler-dependent
    // int result = x++ + ++x;  // DON'T DO THIS!
    
    // ✅ Safe approach - separate the operations
    int safe_x = 5;
    int first = safe_x++;      // safe_x becomes 6, first gets 5
    int second = ++safe_x;     // safe_x becomes 7, second gets 7
    int safe_result = first + second;  // 5 + 7 = 12
    
    std::cout << "Safe calculation: " << first << " + " << second << " = " << safe_result << std::endl;
    
    // More examples of undefined behavior to AVOID:
    // int y = 1;
    // int bad1 = y++ + y++;     // Undefined behavior
    // int bad2 = ++y + y;       // Undefined behavior
    // int bad3 = y++ * ++y;     // Undefined behavior
    
    // ✅ Safe versions:
    int y = 1;
    int temp1 = y++;           // y becomes 2, temp1 = 1
    int temp2 = y++;           // y becomes 3, temp2 = 2
    int good1 = temp1 + temp2; // 1 + 2 = 3
    
    std::cout << "Safe version: " << temp1 << " + " << temp2 << " = " << good1 << std::endl;
    
    return 0;
}

Output:

Safe calculation: 5 + 7 = 12
Safe version: 1 + 2 = 3

Common side effect mistakes

#include <iostream>

int main()
{
    // ❌ Mistake 1: Modifying array index while accessing
    int arr[] = {1, 2, 3, 4, 5};
    int i = 0;
    
    // This is undefined behavior - don't do this!
    // arr[i] = i++; // Which i is used for the index?
    
    // ✅ Safe approach
    arr[i] = i;    // Use current value
    i++;           // Then increment
    
    // ❌ Mistake 2: Multiple increments in function parameters
    auto print2Values = [](int a, int b) {
        std::cout << "Values: " << a << ", " << b << std::endl;
    };
    
    int counter = 1;
    // print2Values(counter++, counter++); // Undefined behavior!
    
    // ✅ Safe approach
    int first = counter++;    // counter becomes 2, first = 1
    int second = counter++;   // counter becomes 3, second = 2
    print2Values(first, second);
    
    // ❌ Mistake 3: Self-assignment with increment
    i = 2;
    // i = i++; // Undefined behavior - is i assigned before or after increment?
    
    // ✅ Just use increment by itself if that's what you want
    ++i; // or i++; Clear intention
    
    std::cout << "Final i value: " << i << std::endl;
    
    return 0;
}

Output:

Values: 1, 2
Final i value: 3

Practical applications

Loop counters

#include <iostream>

int main()
{
    std::cout << "Forward counting with increment:" << std::endl;
    for (int i = 1; i <= 5; ++i) {
        std::cout << "Count: " << i << std::endl;
    }
    
    std::cout << "\nBackward counting with decrement:" << std::endl;
    for (int i = 5; i >= 1; --i) {
        std::cout << "Countdown: " << i << std::endl;
    }
    
    // While loop with increment
    std::cout << "\nWhile loop example:" << std::endl;
    int counter = 0;
    while (counter < 3) {
        std::cout << "Counter: " << counter << std::endl;
        ++counter;  // Increment at end of loop
    }
    
    return 0;
}

Output:

Forward counting with increment:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5

Backward counting with decrement:
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1

While loop example:
Counter: 0
Counter: 1
Counter: 2

Array traversal

#include <iostream>

int main()
{
    int numbers[] = {10, 20, 30, 40, 50};
    int size = 5;
    
    // Forward traversal with postfix increment
    std::cout << "Forward traversal:" << std::endl;
    for (int i = 0; i < size; i++) {  // i++ commonly used in for loops
        std::cout << "numbers[" << i << "] = " << numbers[i] << std::endl;
    }
    
    // Backward traversal with prefix decrement
    std::cout << "\nBackward traversal:" << std::endl;
    for (int i = size - 1; i >= 0; --i) {  // --i slightly more efficient
        std::cout << "numbers[" << i << "] = " << numbers[i] << std::endl;
    }
    
    // Processing with increment
    std::cout << "\nDoubling all values:" << std::endl;
    for (int i = 0; i < size; ++i) {
        numbers[i] *= 2;
        std::cout << "numbers[" << i << "] = " << numbers[i] << std::endl;
    }
    
    return 0;
}

Output:

Forward traversal:
numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50

Backward traversal:
numbers[4] = 50
numbers[3] = 40
numbers[2] = 30
numbers[1] = 20
numbers[0] = 10

Doubling all values:
numbers[0] = 20
numbers[1] = 40
numbers[2] = 60
numbers[3] = 80
numbers[4] = 100

Practical counting and processing

#include <iostream>

int main()
{
    // Count positive and negative numbers
    int numbers[] = {5, -3, 8, -1, 0, 4, -7, 2};
    int size = 8;
    int positiveCount = 0;
    int negativeCount = 0;
    int zeroCount = 0;
    
    for (int i = 0; i < size; ++i) {
        if (numbers[i] > 0) {
            ++positiveCount;
        } else if (numbers[i] < 0) {
            ++negativeCount;
        } else {
            ++zeroCount;
        }
    }
    
    std::cout << "Number analysis:" << std::endl;
    std::cout << "Positive numbers: " << positiveCount << std::endl;
    std::cout << "Negative numbers: " << negativeCount << std::endl;
    std::cout << "Zeros: " << zeroCount << std::endl;
    
    // Process until condition met
    std::cout << "\nProcessing until sum exceeds 50:" << std::endl;
    int values[] = {5, 10, 15, 20, 25, 30};
    int sum = 0;
    int index = 0;
    
    while (sum <= 50 && index < 6) {
        sum += values[index];
        std::cout << "Added " << values[index] << ", sum is now " << sum << std::endl;
        ++index;
    }
    
    return 0;
}

Output:

Number analysis:
Positive numbers: 4
Negative numbers: 3
Zeros: 1

Processing until sum exceeds 50:
Added 5, sum is now 5
Added 10, sum is now 15
Added 15, sum is now 30
Added 20, sum is now 50
Added 25, sum is now 75

Best practices

1. Prefer prefix when the return value isn't used

// ✅ In loops, prefer prefix (slightly more efficient for complex types)
for (int i = 0; i < 10; ++i) {  // Preferred
    // Loop body
}

// This works too, but postfix creates a temporary copy
for (int i = 0; i < 10; i++) {  // Acceptable for built-in types
    // Loop body  
}

2. Use separate statements for clarity

// ❌ Confusing - what happens when?
int result = arr[index++] + arr[++index];

// ✅ Clear and safe
int first = arr[index];
++index;
int second = arr[index];
++index;
int result = first + second;

3. Be consistent in your usage

// ✅ Pick a style and stick with it
for (int i = 0; i < size; ++i) {    // Always use prefix in loops
    for (int j = 0; j < cols; ++j) {  // Consistent
        // Process element
    }
}

4. Avoid multiple modifications in one statement

// ❌ Undefined behavior
// int x = i++ + ++i;

// ✅ Split into clear steps
int temp1 = i++;
int temp2 = ++i;
int x = temp1 + temp2;

Summary

Increment and decrement operators provide convenient shortcuts for common operations:

Key concepts:

  • Increment (++): Adds 1 to a variable
  • Decrement (--): Subtracts 1 from a variable
  • Prefix (++x): Increment first, return new value
  • Postfix (x++): Return current value, then increment

Critical differences:

  • When used alone: prefix and postfix behave identically
  • When return value is used: prefix and postfix behave differently
  • Prefix is often preferred in loops (more efficient for complex types)

Side effects and safety:

  • Multiple modifications between sequence points cause undefined behavior
  • Always separate complex operations into clear steps
  • Avoid modifying the same variable multiple times in one expression

Best practices:

  • Use prefix when the return value isn't needed
  • Keep operations simple and clear
  • Separate complex expressions into multiple statements
  • Be consistent in your coding style

Understanding these operators and their subtleties helps you write more efficient code and avoid hard-to-debug problems caused by undefined behavior.

Quiz

  1. What value is printed by this code?

    int x = 5;
    cout << ++x;
    

    a) 5 b) 6 c) Undefined d) Compiler error

  2. What value is printed by this code?

    int x = 5;
    cout << x++;
    

    a) 5 b) 6 c) Undefined d) Compiler error

  3. After this code executes, what is the value of x?

    int x = 10;
    int y = ++x;
    

    a) 10 b) 11 c) Undefined d) 0

  4. Which of these expressions causes undefined behavior? a) ++x b) x++ c) x++ + ++x d) ++x; y = x;

  5. Which is generally preferred in for loops? a) i++ always b) ++i when return value not used c) No difference d) Depends on the loop type

Practice exercises

Try these exercises to master increment/decrement operators:

  1. Counter Program: Write a program that demonstrates the difference between prefix and postfix operators in various scenarios.

  2. Array Processor: Create a program that uses increment/decrement operators to traverse and modify array elements safely.

  3. Loop Variations: Write the same counting loop using prefix increment, postfix increment, and traditional assignment to see the differences.

  4. Side Effect Debugger: Find and fix undefined behavior in expressions that incorrectly use multiple increment/decrement operators.

Continue Learning

Explore other available lessons while this one is being prepared.

View Course

Explore More Courses

Discover other available courses while this lesson is being prepared.

Browse Courses

Lesson Discussion

Share your thoughts and questions

💬

No comments yet. Be the first to share your thoughts!

Sign in to join the discussion