C++ • Syntax and Core Types

Increment/Decrement Operators — Use increment/decrement operators and understand pre/post behavior (intro)

Master the subtle distinction between prefix and postfix forms and avoid common pitfalls in expression evaluation.

Programming braces with a command terminal and binary digits.
Inside this lesson 10 sections 5 questions 9 figures

SECTION 1

Historical Context & Motivation

The increment and decrement operators are among the most iconic syntactic features of the C family of languages, and their origin is tightly intertwined with the hardware culture of early computing. In the late 1960s, Ken Thompson and Dennis Ritchie were developing the B and C programming languages at Bell Labs on a PDP-7 and later a PDP-11 minicomputer. The PDP-11 instruction set included auto-increment and auto-decrement addressing modes that could adjust a register's value before or after it was used as an address, allowing a single machine instruction to both access memory and bump a pointer. Thompson recognized that exposing this hardware capability directly through language syntax would yield tighter, more efficient compiled code, and so the ++ and -- operators — in both prefix and postfix forms — became first-class citizens of the B language and then of C.

1969 • B Language at Bell Labs

Ken Thompson introduces ++ and -- in the B language, directly motivated by the PDP-7's auto-increment register hardware.

1972 • C Language Inherits the Operators

Dennis Ritchie develops C from B, preserving both prefix and postfix forms. The PDP-11's addressing modes make the distinction between pre- and post-increment semantically meaningful at the hardware level.

1979 • Bjarne Stroustrup Begins C++

Stroustrup extends C with classes, eventually naming the language C++ — a playful use of the post-increment operator to signal 'one step beyond C.'

1998 • ISO C++98 Standard

The first ISO standard formalizes the semantics of increment and decrement operators, including overloading conventions for user-defined types with a dummy int parameter to distinguish prefix from postfix overloads.

Even though modern compilers can optimize away the hardware-level distinction in many contexts, the semantic difference between prefix and postfix forms remains critically important. A misunderstanding of when the modification occurs relative to the value used in the expression continues to be a common source of bugs in both student and professional code. This lesson dissects exactly how each form works, so that you can use them with precision and confidence.

SECTION 2

Core Principles & Definitions

The increment and decrement operators are unary operators — they take a single operand, which must be a modifiable lvalue (an expression that refers to a memory location capable of being assigned to). The operators exist in two syntactic forms: prefix (++x / --x) and postfix (x++ / x--). Both forms have the same side effect — they add or subtract 1 from the operand — but they differ in the value they yield to the surrounding expression.

1

Prefix Increment (++x)

Increments the operand first, then yields the new value as the result of the expression. The result is an lvalue in C++.
2

Postfix Increment (x++)

Saves a copy of the operand's original value, increments the operand, then yields the saved copy. The result is a prvalue (a temporary).
3

Prefix Decrement (--x)

Decrements the operand first, then yields the new (decremented) value. Like prefix increment, the result is an lvalue.
4

Postfix Decrement (x--)

Saves the original value, decrements the operand, and yields the old copy as a prvalue. Conceptually symmetric with postfix increment.
5

Operand Requirement: Modifiable Lvalue

The operand must designate a writable memory location. You cannot write 5++ or ++(a+b) because literals and temporaries are not modifiable lvalues.
✦

KEY TAKEAWAY

Think of a bank teller processing a transaction. Prefix is like depositing money and then checking your balance — you see the updated amount. Postfix is like checking your balance receipt and then having the deposit applied — the receipt shows the old amount, even though your account has already been credited.

SECTION 3

Visual Explanation — Prefix vs. Postfix in Action

Prefix vs. Postfix Increment: Step-by-Step Memory ViewPREFIX: int y = ++x;Step 1x starts at 55Step 2x incremented → 66Step 3y receives NEW value6Result: x = 6, y = 6POSTFIX: int y = x++;Step 1x starts at 55Step 2y receives OLD value5Step 3x incremented → 66Result: x = 6, y = 5Key Insight: Both mutate x to 6increment happensPREFIX: yields herevalue is usedPOSTFIX: yields old value← timeline of a single expression evaluation →The side effect (incrementing x) is identical; only the yielded value differs.
The left panel illustrates prefix increment: the variable is modified first and the new value is yielded. The right panel shows postfix increment: the old value is captured and yielded, and the increment occurs as a side effect. The bottom timeline emphasizes that the mutation is identical — only the moment at which the expression's value is determined differs.

The diagram above makes the central distinction concrete. In the prefix case (int y = ++x;), the compiler conceptually performs two operations in sequence: first it increments x from 5 to 6, then it evaluates the expression ++x as 6 and stores that in y. In the postfix case (int y = x++;), the expression x++ evaluates to the original value 5, which is assigned to y, and then x is incremented to 6 as a side effect. After both statements, x holds 6 in either case — the only difference is what y received.

SECTION 4

How It Works — Evaluation Semantics

Understanding the operators deeply requires thinking about them in terms of value categories and sequencing. In the C++ standard, the prefix forms are defined to return an lvalue reference to the modified operand, while the postfix forms return a prvalue copy of the operand's original value. This difference has practical consequences: because prefix returns an lvalue, you can chain operations like ++++x (incrementing twice), whereas x++++ is ill-formed because the first postfix returns a prvalue, which is not a modifiable lvalue.

PREFIX INCREMENT SEMANTICS

++x≡(x=x+1,yieldsx)++x ≡ (x = x + 1, yields x)

The variable x is incremented, and the expression evaluates to the new value. The result is an lvalue — a reference to x itself.

POSTFIX INCREMENT SEMANTICS

x++≡(temp=x,x=x+1,yieldstemp)x++ ≡ (temp = x, x = x + 1, yields temp)

A temporary temp captures the original value of x. Then x is incremented. The expression yields temp — a prvalue (an rvalue temporary).

PREFIX DECREMENT SEMANTICS

−−x≡(x=x−1,yieldsx)--x ≡ (x = x − 1, yields x)

Mirrors prefix increment but subtracts 1. The result is still an lvalue.

POSTFIX DECREMENT SEMANTICS

x−−≡(temp=x,x=x−1,yieldstemp)x-- ≡ (temp = x, x = x − 1, yields temp)

Mirrors postfix increment. The temporary holds the pre-decrement value, and that is what the expression yields.

⚠

Undefined Behavior Warning

In C++, if a scalar variable is modified more than once between two sequence points (or, in C++11 terms, if two side effects on the same scalar are unsequenced), the behavior is undefined. For example, i = i++ + ++i; is undefined behavior. Never modify a variable more than once in the same full-expression unless there is a sequencing guarantee (such as the comma operator, &&, ||, or ?:).

SECTION 5

Detailed Breakdown — Pre vs. Post in Context

To solidify the distinction, let us trace through several common usage patterns. The difference between prefix and postfix is most visible when the operator's result is used — for instance, assigned to another variable, passed as a function argument, or embedded in a compound expression. When the result is discarded (as in a standalone statement x++; or ++x;), the observable effect is identical for built-in types.

Trace Table: Four Scenarios#Codex beforeExpression Valuex afterAint y = ++x;34 (new)4Bint y = x++;33 (old)4Cint y = --x;32 (new)2Dint y = x--;33 (old)2Common Loop Usagefor (int i = 0; i < n; ++i) // ++i preferred: no temp copyfor (int i = 0; i < n; i++) // i++ works identically hereWhen the result is discarded (as ina for-loop update), both are equivalentfor built-in types. Prefer ++i by convention.Result-Used Scenarioarr[i++] = val;// writes to arr[old_i], then bumps iarr[++i] = val;// bumps i first, writes to arr[new_i]⚠ These index different elements!Off-by-one bugs often come fromchoosing the wrong form here.
The trace table (rows A–D) shows all four operators applied to x = 3. Prefix forms yield the modified value; postfix forms yield the original value. The bottom panels illustrate that in loop updates the choice is stylistic, but in array indexing the choice is semantically critical.
FeaturePrefix (++x / --x)Postfix (x++ / x--)
Value yieldedNew (after modification)Old (before modification)
Value category of resultlvalueprvalue
Can be further modified?Yes (e.g., ++++x)No — result is a temporary
Performance for iteratorsPotentially faster — no copy neededCreates a copy of the original
Typical idiomatic useLoop updates, "increment then use""Use then increment" (e.g., arr[i++])

Prefix vs. Postfix: Key Differences

SECTION 6

Worked Example — Tracing Through Code

Let us trace through a small program that uses both prefix and postfix operators in different contexts. The goal is to predict the exact console output.

ℹ

Source Code

#include <iostream> int main() { int a = 5; int b = a++; // Line 1 int c = ++a; // Line 2 int d = --c + b--; // Line 3 std::cout << a << " " << b << " " << c << " " << d << std::endl; return 0; }

Step-by-Step Trace

  1. 1

    Step 1 — Line 1: int b = a++;

    The postfix operator yields the old value of a before incrementing. Since a starts at 5, b receives 5. Then a is incremented to 6.
    a = 6, b = 5
  2. 2

    Step 2 — Line 2: int c = ++a;

    The prefix operator increments a first (from 6 to 7), then yields the new value. So c receives 7.
    a = 7, c = 7
  3. 3

    Step 3 — Line 3: int d = --c + b--;

    Two operators appear here. --c is prefix decrement: c goes from 7 to 6, and the expression yields 6. b-- is postfix decrement: the expression yields the old value of b (5), and then b is decremented to 4. The addition is 6 + 5 = 11.
    c = 6, b = 4, d = 11
  4. 4

    Step 4 — Final Output

    Printing a << " " << b << " " << c << " " << d gives us the final state of all four variables.
    Output: 7 4 6 11

SECTION 7

Common Idioms and Pitfalls

Knowing the rules is only half the battle; knowing when to prefer each form is equally important. The C++ community has developed strong conventions around increment and decrement operators that balance readability, correctness, and (for user-defined types) performance.

ScenarioRecommended FormRationale
Loop counter update (result discarded)++iConvention; avoids unnecessary copy for iterators and user-defined types.
"Use current value, then advance"i++Postfix is the natural choice when you need the old value (e.g., *p++ in pointer idioms).
Standalone statementEither (prefer ++i)For built-in types, compilers optimize both identically. Prefix is the community default.
Complex expression with multiple side effectsAvoidMixing increment/decrement of the same variable in one expression risks undefined behavior.
Iterator traversal in STL++itPostfix requires copying the iterator. For complex iterators (e.g., std::map::iterator), prefix can be measurably faster.

When to Use Which Form

✦

KEY TAKEAWAY

Think of increment/decrement operators like turnstile gates in a subway system. A prefix gate clicks the counter before you walk through, so the display already shows the new count as you pass. A postfix gate lets you through showing the old count and only clicks after you have passed. The total number of passengers recorded is the same either way — it is only the count displayed at the moment of passage that differs. When you do not check the display (i.e., the result is discarded), it does not matter which gate you use.

SECTION 8

Connection to Operator Overloading

For built-in types, the compiler handles increment and decrement natively. However, one of C++'s distinctive features is operator overloading, which allows user-defined types (classes) to provide custom implementations of ++ and --. The C++ standard distinguishes the two overloads via a dummy int parameter in the postfix signature. Understanding this convention is essential when you move on to implementing custom iterators or smart counters.

AspectBuilt-in Types (int, double*, etc.)User-Defined Types (Classes)
Prefix signatureHandled by compiler intrinsicT& operator++()
Postfix signatureHandled by compiler intrinsicT operator++(int) — the int is a dummy parameter
Return typePrefix: lvalue; Postfix: prvalueConvention mirrors built-in: prefix returns T&, postfix returns T
Performance differenceTypically optimized awayPostfix constructs a copy — can be significant for complex types
When to learnNow (this lesson)After classes, operator overloading, and iterators

Built-in vs. User-Defined Increment/Decrement

As you advance to topics like STL iterators, move semantics, and custom container design, the prefix-vs.-postfix distinction will become even more consequential. The postfix overload for a heavyweight iterator might involve copying an entire state machine, making ++it meaningfully faster than it++. This is why the modern C++ guideline is clear: prefer prefix unless you specifically need the old value.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain in your own words why ++x returns an lvalue while x++ returns a prvalue. Why does this distinction matter for chaining operations?
Show answer
The prefix operator modifies the operand and then returns a reference to the operand itself — the same memory location — hence an lvalue. The postfix operator must yield the value the operand held before modification, so it creates a temporary copy of the old value and returns that copy, which is a prvalue (a temporary with no persistent address). This matters because an lvalue can appear on the left side of an assignment or be passed to a function expecting a non-const reference, and it can be incremented again (e.g., ++++x is valid). A prvalue cannot be assigned to or further modified, so x++++ is a compilation error.

Question 2: BASIC CALCULATION

Given the following code, what are the final values of x, y, and z? int x = 10; int y = x--; int z = --x;
Show answer
Starting with x = 10: the postfix x-- yields the old value 10 to y, then decrements x to 9. Next, the prefix --x decrements x from 9 to 8, and yields 8 to z. Final values: x = 8, y = 10, z = 8.

Question 3: INTERMEDIATE

What does the following code print, and why? int a = 3; int b = ++a * a++; std::cout << a << " " << b << std::endl; Hint: consider the C++ sequencing rules.
Show answer
This code exhibits undefined behavior. The variable a is modified twice in the same full-expression (++a and a++) without an intervening sequence point. The C++ standard does not specify the order in which the two side effects occur relative to each other, so any output is possible and the compiler is free to produce any result. The correct answer to 'what does this print?' is 'it is undefined — the code is ill-formed with respect to sequencing and should never be written.'

Question 4: APPLIED

You are implementing a ring buffer with a fixed-size array of capacity N and a write index writeIdx. Write a single expression that stores a value val at the current write position and then advances writeIdx to the next slot (wrapping around). Use the appropriate form of the increment operator.
Show answer
The idiomatic expression is: buffer[writeIdx++ % N] = val; — but note that writeIdx may grow unboundedly, so it is often better to write: buffer[writeIdx] = val; writeIdx = (writeIdx + 1) % N; for clarity. The postfix writeIdx++ is specifically correct here because we need the old index for the array subscript. Using prefix ++writeIdx would skip slot 0 and write to the wrong position. This is a classic scenario where the choice between pre and post directly affects program correctness.

Question 5: CRITICAL THINKING

Consider a class BigInt that represents arbitrarily large integers using a std::vector<uint8_t> of digits. Discuss why the performance difference between prefix and postfix ++ could be significant for this type, and sketch the signatures and key implementation differences of the two overloaded operators.
Show answer
The prefix operator BigInt& operator++() modifies *this in place (adding 1 via carry propagation) and returns a reference — no allocation or copy occurs beyond the mutation. The postfix operator BigInt operator++(int) must first construct a copy of *this (which deep-copies the entire digit vector), then increment *this, and return the copy by value. For a BigInt with thousands of digits, this copy is O(n) in the number of digits — a potentially significant cost. This analysis generalizes: for any type whose copy constructor is expensive, postfix increment has a measurable overhead that prefix avoids. This is the core reason behind the C++ guideline to prefer prefix when the result is not used.

SUMMARY

Lesson Summary

Summary

C++ provides four unary operators for adjusting a variable by one: prefix increment (++x), postfix increment (x++), prefix decrement (--x), and postfix decrement (x--). All four share the same side effect — adding or subtracting 1 — but they differ critically in the value they yield to the surrounding expression. Prefix forms modify the operand first and return the new value as an lvalue; postfix forms capture the old value as a prvalue and then apply the modification.

When the expression result is discarded (e.g., in a for-loop update), both forms produce identical observable behavior for built-in types, but the C++ community convention is to prefer prefix because it avoids creating unnecessary temporaries — a consideration that becomes performance-relevant for user-defined types and iterators. Finally, never modify the same variable more than once in a single expression without guaranteed sequencing, as doing so invokes undefined behavior.

Varsity Tutors • C++ • Increment/Decrement Operators