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 + 2skips every other value. - The condition
i <= Nis 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 <= limitkeeps 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 <= nis 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
forheader 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 onb. - 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 fixedN. - 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.,
iup whilejdown).
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.
