Beyond “+1 Until N”: Non-Standard Steps and Checks in for Loops

Once the basic for pattern clicks (i = 0; i < N; i = i + 1), the next hurdle is realizing you can tune both the step and the check to match the problem. Many tasks aren’t “count by ones”; they skip elements, grow geometrically, or stop when a mathematical condition becomes false.

This post shows practical variations of the step and check parts—still the classic for, just tailored to the job.

Quick reminder: the three parts

for (initialization; condition; step) {
    // body
}
  • initialization runs once,
  • condition is checked before each round,
  • step runs after each round.

Change the condition to control when the loop stops, and the step to control how the counter moves.

1) Skipping by more than 1 (arithmetic steps)

Use-case: odd/even terms, grid sampling, polynomial evaluation at regular gaps.

#include <iostream>

int main() {
    int N = 10;

    // Print odd numbers up to N (1, 3, 5, ...).
    for (int i = 1; i <= N; i = i + 2) {
        std::cout << i << ' ';
    }
    std::cout << '\n';

    return 0;
}
  • Step i = i + 2 skips every other value.
  • The condition i <= N is inclusive; choose < vs <= carefully (off-by-one is the common bug).

2) Geometric growth (multiplicative steps)

Use-case: doubling an interval, estimating how many times you must double to exceed a threshold (log₂-type reasoning).

#include <iostream>

int main() {
    int limit = 200;
    for (int x = 1; x <= limit; x = x * 2) {
        std::cout << x << ' ';   // 1 2 4 8 16 32 64 128
    }
    std::cout << '\n';
    return 0;
}
  • The step multiplies instead of adding.
  • The check x <= limit keeps the sequence bounded.

3) Non-linear checks (stop by a property, not by a count)

Use-case: iterate divisors only up to √n.

#include <iostream>

int main() {
    int n = 60;

    // Test candidates only while i*i <= n
    for (int i = 1; i * i <= n; i = i + 1) {
        if (n % i == 0) {
            std::cout << i << " and " << (n / i) << " are divisors\n";
        }
    }
    return 0;
}
  • The condition i * i <= n is mathematically driven.
  • This avoids unnecessary iterations beyond √n.

Note, I have introduced the % operator here. This is the modulo operator that evaluates to the remainder after integer division. I will talk more about operators in a future post.

4) Two counters that move together

Use-case: symmetric formulas, pairing terms (i with N−i), walking from both ends toward the middle.

#include <iostream>

int main() {
    int N = 6;

    // i goes up, j goes down
    for (int i = 0, j = N; i <= j; i = i + 1, j = j - 1) {
        std::cout << "pair: " << i << " & " << j << '\n';
    }
    return 0;
}
  • The for header allows comma-separated expressions in initialization and step.
  • Use this to keep related counters in one place. (Keep it readable—don’t pack unrelated work here.)

5) Floating-point steps (numerical loops)

Use-case: Riemann sums and simple time stepping.

#include <iostream>

int main() {
    double a = 0.0, b = 1.0, h = 0.2;

    // Sample points a, a+h, a+2h, ... while t <= b (inclusive bound)
    for (double t = a; t <= b + 1e-12; t = t + h) {
        std::cout << "t = " << t << '\n';
    }
    return 0;
}
  • Floating comparisons can be sensitive to rounding; a tiny tolerance (+ 1e-12) helps when you expect to land exactly on b.
  • Pick <= vs < consciously depending on whether you want to include the endpoint.

6) Stopping by accuracy, not by count (adaptive end condition)

Use-case: Taylor series until the next term is small.

#include <iostream>

int main() {
    double x = 1.0;        // evaluate e^x at x = 1
    double term = 1.0;     // current term in the series
    double sum  = 1.0;     // starts with 1
    double eps  = 1e-6;

    // k counts the term index (1,2,3,...)
    for (int k = 1; term > eps; k = k + 1) {
        term = term * (x / k); // next term: x^k / k!
        sum  = sum + term;
    }

    std::cout << "Approx e^1 = " << sum << '\n';
    return 0;
}
  • The check depends on the size of the next contribution (term > eps), not a fixed N.
  • This is common in numerical methods where you stop when “good enough.”

7) Counting down (custom check + step)

Use-case: finite difference stencils, reverse traversal, countdown timers.

#include <iostream>

int main() {
    for (int k = 10; k >= 0; k = k - 2) {  // 10, 8, 6, ..., 0
        std::cout << k << ' ';
    }
    std::cout << '\n';
    return 0;
}
  • Match initialization, condition, and step so the loop makes progress toward stopping.

Practical guidance

  • Make progress obvious. When you choose the check, choose a step that will eventually make it false.
  • Prefer clarity over cleverness. Non-standard headers are powerful, but don’t hide unrelated side effects in the step.
  • Inclusive vs. exclusive bounds is still the top source of bugs—double-check < vs <=.
  • Floating-point checks may need a tolerance.
  • Multiple counters are fine when they belong together (e.g., i up while j down).

Key takeaways

  • You can (and should) adapt the step and check to the math of your problem.
  • Steps can add, subtract, multiply, or update multiple counters; checks can be property-based (e.g., i*i <= n) or accuracy-based (e.g., term > eps).
  • Clear, problem-aligned headers reduce code in the body and cut errors.

In the next posts, we’ll explore break and continue, range-based loops, and nested loops—tools that build on these fundamentals.