Intermediate 11 min

Range-Based For Loops

Simplify iteration with C++11's range-based for loops for cleaner, safer code

Learn how to iterate over containers safely and elegantly using C++11's range-based for loops.

A Simple Example

#include <iostream>
#include <vector>
#include <string>

int main() {
    std::vector<int> numbers{10, 20, 30, 40, 50};

    // Old style - error prone
    for (size_t i{0}; i < numbers.size(); ++i) {
        std::cout << numbers[i] << " ";
    }
    std::cout << "\n";

    // Range-based - clean and safe
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << "\n";

    // With auto for type deduction
    for (auto num : numbers) {
        std::cout << num << " ";
    }
    std::cout << "\n";

    // Modifying elements - use reference
    for (auto& num : numbers) {
        num *= 2;  // Double each number
    }

    // Read-only - use const reference (avoids copies)
    for (const auto& num : numbers) {
        std::cout << num << " ";
    }
    std::cout << "\n";

    return 0;
}

Breaking It Down

Basic Range-Based For Syntax

  • Syntax: for (type variable : container) { }
  • What it does: Iterates through each element in the container automatically
  • No indices needed: Eliminates off-by-one errors and index management
  • Remember: Much cleaner than traditional for loops with manual indexing

Using auto for Type Deduction

  • Syntax: for (auto item : container) - compiler deduces the type
  • Benefit: Less typing, works with complex types, adapts to container changes
  • Creates copies: Each iteration copies the element unless you use a reference
  • Remember: auto makes your code more flexible and maintainable

Modifying Elements with References

  • Syntax: for (auto& item : container) - note the ampersand
  • What it does: Gives you a reference to the original element, not a copy
  • Use when: You need to modify elements in place
  • Remember: Without &, you modify a copy that gets discarded

Read-Only Access with const auto&

  • Syntax: for (const auto& item : container)
  • Benefits: No copying (fast) + prevents accidental modification (safe)
  • Best for: Large objects like strings or custom types
  • Remember: This is the recommended default for read-only iteration

Why This Matters

  • Traditional for loops with indices are error-prone: off-by-one errors, wrong comparison operators, forgetting to increment.
  • Range-based for loops eliminate these bugs by iterating directly over elements.
  • They're safer, clearer, and work with any container - arrays, vectors, maps, or even your custom types.

Critical Insight

Range-based for loops use iterators behind the scenes. When you write for (auto x : container), the compiler generates code equivalent to for (auto it = container.begin(); it != container.end(); ++it).

This means range-based loops work with ANY type that has begin() and end() methods - including your custom containers! The compiler does all the iterator management for you.

Best Practices

Default to const auto& for read-only iteration: Avoids unnecessary copies and prevents accidental modification. This is the safest and most efficient choice.

Use auto& when modifying elements: Only use a non-const reference when you actually need to change the elements in place.

Avoid modifying container size during iteration: Don't add or remove elements while iterating - it invalidates iterators and causes undefined behavior.

Use traditional loops when you need indices: If you need the position or want to skip elements, a traditional indexed loop might be clearer.

Common Mistakes

Forgetting the reference: for (auto x : vec) copies each element. For large objects, this is slow. Use const auto& instead.

Modifying container while iterating: Don't add/remove elements during range-based iteration - it invalidates iterators and causes crashes.

Using range-based loops for parallel iteration: If you need to iterate over multiple containers together, use traditional indexed loops.

Expecting modification without &: Without a reference, changes to the loop variable don't affect the original container.

Debug Challenge

This code tries to double all numbers in a vector but it doesn't work. Click the highlighted line to fix it:

1 #include <iostream>
2 #include <vector>
3
4 int main() {
5 std::vector<int> numbers{1, 2, 3, 4, 5};
6
7 for (auto num : numbers) {
8 num *= 2;
9 }
10
11 for (auto n : numbers) {
12 std::cout << n << " ";
13 }
14 std::cout << "\n";
15
16 return 0;
17 }

Quick Quiz

  1. How do you modify elements in a range-based for loop?
for (auto& x : container)
for (auto x : container)
for (auto* x : container)
  1. What's the benefit of const auto& over auto?
It's faster
It prevents modification and avoids copying
It's required for all containers
  1. Can you use range-based for with C-style arrays?
No, only with STL containers
Only if you convert to std::array first
Yes, it works with arrays too

Practice Playground

Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.

Lesson Progress

  • Fix This Code
  • Quick Quiz
  • Practice Playground - run once