Question

How to Iterate Over a Range with a Custom Step in Rust

rustiterator

Question

How can I iterate over a range in Rust using a step value other than 1?

I am coming from a C++ background, where I might write a loop like this:

for (auto i = 0; i <= n; i += 2) {
    // ...
}

In Rust, I typically use ranges such as 0..=n, but I do not see a third argument for specifying a custom step size. What is the idiomatic way to do this in Rust?

Short Answer

By the end of this page, you will understand how Rust ranges work, why they do not directly include a step argument, and how to use iterator methods such as step_by() to loop with custom increments. You will also learn when inclusive and exclusive ranges matter, how to trace execution, and what common mistakes to avoid.

Concept

Rust treats ranges as iterators or values that can become iterators, and iteration behavior is built up by chaining iterator methods.

Unlike C++'s classic for loop syntax, Rust does not usually put initialization, condition, and increment in one line. Instead, Rust encourages you to describe:

  • what sequence you want
  • how that sequence should be transformed
  • what to do with each item

For a custom step size, the key idea is that you start with a range and then apply .step_by(step).

For example:

for i in (0..=n).step_by(2) {
    println!("{}", i);
}

This means:

  • start at 0
  • go up to and including n
  • take every 2nd value

This matters in real programming because many tasks do not move one item at a time. You may need to:

  • process every other index
  • sample data at intervals
  • skip unused values
  • generate sequences like 0, 5, 10, 15

Rust's iterator-based approach is powerful because the same style works for many kinds of sequences, not just numeric loops.

Mental Model

Think of a Rust range like a numbered path:

  • 0..=10 means all stepping stones from 0 through 10
  • .step_by(2) means you choose to land on every second stone

So instead of saying, "increase i by 2 after each loop," Rust says, "build me a sequence that already contains only the values I want."

That is the mindset shift from C++-style looping to Rust-style iteration:

  • C++ often focuses on manually controlling the counter
  • Rust often focuses on describing the values to iterate over
Take Quiz

Syntax and Examples

Basic syntax

for i in (start..end).step_by(step) {
    // use i
}

For an inclusive end:

for i in (start..=end).step_by(step) {
    // use i
}

Example: even numbers up to n

fn main() {
    let n = 10;

    for i in (0..=n).step_by(2) {
        println!("{}", i);
    }
}

Output:

0
2
4
6
8
10

Explanation

  • 0..=n creates an inclusive range from 0 to n
  • .step_by(2) skips every other number
  • for loops over the resulting iterator

Example: exclusive upper bound

fn main() {
    for i in (0..10).step_by(3) {
        println!("{}", i);
    }
}

Output:

0
3
6
9

Here, 10 is not included because 0..10 is exclusive at the end.

Example: starting from a non-zero value

fn main() {
    for i in (5..=15).step_by(5) {
        println!("{}", i);
    }
}

Output:

5
10
15

Step by Step Execution

Consider this code:

fn main() {
    let n = 8;

    for i in (0..=n).step_by(2) {
        println!("value = {}", i);
    }
}

Here is what happens step by step:

  1. let n = 8;

    • A variable n is created with value 8.
  2. (0..=n)

    • Rust creates an inclusive range: 0, 1, 2, 3, 4, 5, 6, 7, 8
  3. .step_by(2)

    • Rust transforms that sequence to take every 2nd value starting from the first one.
    • The resulting values are: 0, 2, 4, 6, 8
  4. for i in ...

    • The loop runs once for each value in that stepped sequence.
  5. Loop iterations:

    • First iteration: i = 0
    • Second iteration: i = 2
    • Third iteration: i = 4
    • Fourth iteration: i = 6
    • Fifth iteration: i = 8
  6. After 8

    • There are no more values in the iterator, so the loop ends.

This is conceptually similar to:

for (int i = 0; i <= 8; i += 2)

But in Rust, the sequence is described first, and the loop simply consumes it.

Real World Use Cases

Custom-step iteration appears often in practical Rust code.

Processing every nth item

You may want to sample data instead of reading every element.

let data = [10, 20, 30, 40, 50, 60];
for i in (0..data.len()).step_by(2) {
    println!("{}", data[i]);
}

Working with grid coordinates

In graphics, game development, or simulations, you might move through positions in fixed intervals.

for x in (0..=100).step_by(10) {
    println!("x = {}", x);
}

Reading structured data

If bytes are grouped in chunks, a stepped index can help.

let bytes = [1, 2, 3, 4, 5, 6, 7, 8];
for i in (0..bytes.len()).step_by(2) {
    println!("pair starts at index {}", i);
}

Scheduling repeated intervals

Stepped ranges are useful for generating fixed time slots, IDs, or batch boundaries.

for minute in (0..60).step_by(15) {
    println!("Run task at minute {}", minute);
}
Take Quiz

Real Codebase Usage

In real projects, developers often combine step_by() with other iterator patterns.

Looping over indices carefully

When working with arrays or slices, stepped loops are often used with lengths:

let items = ["a", "b", "c", "d", "e"];
for i in (0..items.len()).step_by(2) {
    println!("{} -> {}", i, items[i]);
}

This is common when you specifically need the index.

Using guard clauses before looping

If the step depends on input, validate it first.

fn print_with_step(n: usize, step: usize) {
    if step == 0 {
        return;
    }

    for i in (0..=n).step_by(step) {
        println!("{}", i);
    }
}

This avoids invalid logic and makes the function safer.

Prefer direct iteration when possible

In production Rust, developers often avoid indexing unless needed. If you need every second element from a collection, another approach may be clearer depending on the situation.

let values = [10, 20, 30, 40, 50, 60];
for value in values.iter().step_by(2) {
    println!("{}", value);
}

This is often safer because it avoids manual index access.

Combining with filters and maps

Stepped iteration is often only one part of a pipeline.

for x in (0..=20).step_by(2).map(|n| n * n) {
    println!("{}", x);
}

This style is common in Rust codebases because iterator chains are expressive and composable.

Common Mistakes

1. Forgetting parentheses around the range

Broken:

for i in 0..=10.step_by(2) {
    println!("{}", i);
}

Why it is wrong:

  • Method call precedence does not mean what you want here.
  • Rust may try to call step_by on 10 instead of on the range.

Correct:

for i in (0..=10).step_by(2) {
    println!("{}", i);
}

2. Confusing .. with ..=

Broken expectation:

for i in (0..10).step_by(2) {
    println!("{}", i);
}

A beginner may expect 10 to be included, but it is not.

  • 0..10 means 0 through 9
  • 0..=10 means 0 through 10

3. Using a step of zero

Broken:

for i in (0..=10).step_by(0) {
    println!("{}", i);
}

Why it is wrong:

  • A step of 0 is invalid.
  • You should ensure the step is at least 1.

Safer pattern:

fn loop_values(limit: usize, step: usize) {
    if step == 0 {
        println!("step must be greater than 0");
        return;
    }

    for i in (0..=limit).step_by(step) {
        println!("{}", i);
    }
}

4. Expecting negative steps with normal ranges

Rust ranges like 0..10 move forward. If you want to go backward, use .rev() when appropriate.

Example:

for i in (0..=10).rev().step_by(2) {
    println!("{}", i);
}

5. Indexing when direct iteration is clearer

Less idiomatic for simple access:

let values = [10, 20, 30, 40];
for i in (0..values.len()).step_by(2) {
    println!("{}", values[i]);
}

Often better:

let values = [10, 20, 30, 40];
for value in values.iter().step_by(2) {
    println!("{}", value);
}

Use indices only when you actually need them.

Comparisons

Custom-step range vs manual counter loop

ApproachStyleBest forExample
Range + step_by()Idiomatic RustClear numeric iteration(0..=n).step_by(2)
while loop with manual incrementMore manualComplex loop controli += 2 inside loop

for with step_by() vs while

Featurefor + step_by()while
ReadabilityUsually clearerMore verbose
Manual controlLessMore
Risk of forgetting incrementNoneHigher
Idiomatic RustYesSometimes

Example: same logic in two styles

Idiomatic Rust

for i in (0..=10).step_by(2) {
    println!("{}", i);
}

Manual while

fn main() {
    let mut i = 0;
    let n = 10;

    while i <= n {
        println!("{}", i);
        i += 2;
    }
}

Use while when the loop condition is more complicated than a simple sequence. Use for with step_by() when you are iterating through a well-defined range.

Index stepping vs iterator stepping

PatternExampleWhen to use
Step through indices(0..items.len()).step_by(2)You need positions or multiple arrays
Step through values directlyitems.iter().step_by(2)You only need the elements
Take Quiz

Cheat Sheet

Quick syntax

for i in (start..end).step_by(step) {
    // end is excluded
}

for i in (start..=end).step_by(step) {
    // end is included
}

Common examples

for i in (0..10).step_by(2) {}
for i in (0..=10).step_by(2) {}
for x in values.iter().step_by(2) {}

Rules to remember

  • Use .step_by(n) to skip by n
  • Add parentheses around the range: (0..10).step_by(2)
  • .. is exclusive at the end
  • ..= is inclusive at the end
  • step must be greater than 0
  • Prefer iterating over values directly if you do not need indices

Reverse iteration

for i in (0..=10).rev().step_by(2) {
    println!("{}", i);
}

C++ mental translation

C++:

for (int i = 0; i <= n; i += 2)

Rust:

for i in (0..=n).step_by(2) {
    // ...
}

FAQ

How do I increment by 2 in a Rust for loop?

Use .step_by(2) on a range:

for i in (0..=10).step_by(2) {
    println!("{}", i);
}

How do I include the end value in a Rust range?

Use ..= instead of ...

0..=10

This includes 10.

Can I use a negative step in Rust ranges?

Not directly with a normal forward range. For reverse iteration, use .rev() on a range and then step through it.

Why does Rust use step_by() instead of a third range argument?

Rust's iteration model is based on iterators and iterator methods. This keeps ranges simple and lets you build custom behavior by chaining methods.

Should I use indices or iterate over values directly?

If you only need the elements, iterate over values directly:

for value in items.iter().step_by(2) {
    println!("{}", value);
}

Use indices when you need positions.

What happens if the step does not divide the range evenly?

Rust stops when the next stepped value would go past the end. It does not force the final value to match the boundary.

Take Quiz

Mini Project

Description

Build a small Rust program that prints numbered checkpoints for a training session. The checkpoints should be generated at a fixed interval, such as every 5 minutes. This demonstrates how to use a range with step_by() in a realistic scheduling-style task.

Goal

Create a Rust program that prints all checkpoint times from 0 up to a limit using a custom step size.

Requirements

  • Read or define a total session length as an integer
  • Read or define a checkpoint interval greater than 0
  • Print each checkpoint from 0 up to the session length
  • Include the final checkpoint only if it falls exactly on the interval
Take Quiz

Keep learning

Related questions

View more