# For Loop Iteration

> Teaches idiomatic for loop patterns across Python, JavaScript, Go, C/C++, Rust, and shell scripting with anti-patterns, common pitfalls, and best practices.

- Skill: `paulpas/for-loop-iteration` (Agent Skill)
- Install (CLI): `npx skillmds@latest add paulpas/for-loop-iteration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulpas/for-loop-iteration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: paulpas (https://skillmd.com/u/paulpas)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paulpas/for-loop-iteration

---





# For Loop Iteration Patterns

Teaches idiomatic for loop iteration patterns across multiple programming languages, helping you choose the right iteration style for each language and problem. Covers index-based loops, range-based iteration, iterator protocols, list comprehensions, and shell scripting loops — with BAD vs GOOD comparisons and language-specific anti-patterns.

## TL;DR Checklist

- [ ] Choose the most idiomatic loop construct for the target language (e.g., `for...of` in JS, `range()` in Python)
- [ ] Prefer iterating over values directly instead of indices when index is not needed
- [ ] Avoid modifying a collection while iterating — collect changes and apply after
- [ ] Guard against off-by-one errors by verifying boundary conditions explicitly
- [ ] Use language-native iterators (enumerate, zip, range, iterators) before manual counters

---

## When to Use

Use this skill when:

- Implementing iteration logic over arrays, slices, maps, or collections in any language
- Choosing between index-based loops, iterator-based loops, and functional patterns (comprehensions, map/filter)
- Converting loop logic from one language to another and seeking idiomatic equivalents
- Debugging common iteration bugs: off-by-one errors, modifying collections mid-loop, iterator invalidation
- Writing performance-sensitive loops where the iteration pattern affects cache locality or allocation
- Building data transformation pipelines that process elements sequentially

---

## When NOT to Use

Avoid for loop patterns in these situations:

- **Stream/async iteration** — use async iterators, generators, or reactive streams instead of blocking for loops
- **Event-driven processing** — use callbacks, event emitters, or message queues rather than polling loops
- **Infinite polling with busy-wait** — use sleep/yield between polls or observables to avoid CPU spinning
- **Complex nested iteration** — consider declarative data structures (join on maps/dicts) instead of O(n²) nested loops

---

## Core Workflow

1. **Identify the iteration goal** — Determine whether you need indices, values, both, keyed access, or parallel iteration. **Checkpoint:** If you only need values, skip index-based patterns and use native iterators (enumerate, for...of, range, direct iterator).

2. **Select the language-native construct** — Each language has one most-idiomatic approach:
   - Python: `for item in iterable` or list comprehensions
   - JavaScript: `for...of` over arrays; avoid `for...in` on arrays
   - Go: single `for` with `range` over slices, maps, channels
   - C/C++: range-based for (C++) or index-based loop with bounds checking
   - Rust: immutable `.iter()`, mutable `.iter_mut()`, or `.into_iter()`
   - Shell: `for item in list` or C-style `for ((i=0; i<n; i++))`

3. **Implement the iteration body** — Write the core logic inside the loop. **Checkpoint:** Verify the loop body does not mutate the collection being iterated over unless using a deliberate safe pattern (see anti-patterns below).

4. **Handle edge cases** — Empty collections, single-element collections, and boundary conditions. **Checkpoint:** Add a guard clause or assert for empty input when the semantics require at least one element.

5. **Choose aggregation style** — Decide whether to accumulate results via append/collect, use a list/dict comprehension, or apply functional transforms (map/filter/fold). **Checkpoint:** Prefer declarative comprehensions when transforming an entire collection without early exit conditions.

6. **Verify correctness** — Test with empty input, single element, typical data, and boundary values. **Checkpoint:** For index-based loops, manually trace first, middle, and last iterations to confirm bounds are correct.

---

## Implementation Patterns / Reference Guide

### Pattern 1: Python for Loop

Python offers multiple iteration idioms. The core principle is "iterate over values directly; reach for indices only when you actually need them."

```python
from typing import Any

# ✅ GOOD — iterate over values directly (most Pythonic)
def print_items(items: list[str]) -> None:
    """Print each item in the collection."""
    for item in items:
        print(item)


# ✅ GOOD — when index is needed, use enumerate
def indexed_display(items: list[str]) -> list[tuple[int, str]]:
    """Return a list of (index, value) pairs."""
    return [(i, item) for i, item in enumerate(items)]


# ✅ GOOD — range-based iteration (useful for step or index math)
def every_second_element(values: list[float]) -> list[float]:
    """Return elements at even indices using range with a step."""
    result: list[float] = []
    for i in range(0, len(values), 2):
        result.append(values[i])
    return result


# ✅ GOOD — list comprehension (preferred over explicit loop for transforms)
def squared_positive(numbers: list[float]) -> list[float]:
    """Return squared values, filtering out negatives."""
    return [n ** 2 for n in numbers if n >= 0]


# ❌ BAD — index-based loop when value iteration suffices
def bad_print_items(items: list[str]) -> None:
    for i in range(len(items)):      # Unnecessary len() call
        print(items[i])               # Double indexing instead of direct access


# ❌ BAD — modifying collection while iterating (RuntimeError or undefined behavior)
def bad_remove_evens(values: list[int]) -> list[int]:
    for v in values:                  # Modifying 'values' during iteration is unsafe
        if v % 2 == 0:
            values.remove(v)          # RuntimeError or skipped elements
    return values
```

**Key Python principles:**
- Prefer `for item in collection` over `range(len(collection))` — it avoids double indexing and works with any iterable (not just sequences)
- Use `enumerate()` when both index and value are needed
- Use list/dict comprehensions for simple transforms — they are faster than explicit `for` + `append`
- Never modify a list while iterating over it; use filtering instead: `[x for x in lst if condition(x)]`

---

### Pattern 2: JavaScript for Loop

JavaScript has three loop constructs for iteration. The modern standard favors `for...of` and array methods over the classic C-style loop.

```javascript
// ✅ GOOD — for...of iterates over values (arrays, strings, Maps, Sets)
function printItems(items) {
    for (const item of items) {
        console.log(item);
    }
}


// ✅ GOOD — for...of with destructuring (array of objects)
function logNames(users) {
    for (const { name, role } of users) {
        console.log(`${name}: ${role}`);
    }
}


// ✅ GOOD — traditional C-style for when you need the index
function doubleEveryOther(numbers) {
    const result = [];
    for (let i = 0; i < numbers.length; i++) {
        if (i % 2 === 1) {
            result.push(numbers[i] * 2);
        } else {
            result.push(numbers[i]);
        }
    }
    return result;
}


// ✅ GOOD — for...of with break/continue for early exit
function findFirstEven(items) {
    for (const item of items) {
        if (item % 2 === 0) {
            return item;       // Early exit on first match
        }
    }
    return null;               // No even number found
}


// ❌ BAD — for...in iterates over enumerable property keys, NOT array values
function bad_forInLoop(items) {
    const results = [];
    for (const index in items) {       // Iterates over "0", "1", "2" as strings
        results.push(items[index] * 2);   // index is a string! Unexpected behavior
    }
    return results;
}

// ❌ BAD — modifying array during for...of iteration (skipped elements)
function bad_removeFalsy(arr) {
    for (const val of arr) {           // Modifying arr during iteration skips items
        if (!val) {
            arr = arr.filter(v => v);  // Creates new array, but loop still runs on old state conceptually
        }
    }
}


// ✅ GOOD — use array methods instead of loops for common transforms
function filterActiveUsers(users) {
    return users.filter(user => user.active);
}

function transformPrices(prices, taxRate) {
    return prices.map(price => price * (1 + taxRate));
}
```

**Key JavaScript principles:**
- `for...of` iterates over **values** — use this for arrays, strings, Maps, Sets
- `for...in` iterates over **enumerable property keys** — NEVER use it on arrays; only on plain objects
- C-style `for` is fine when you need the index or a custom step
- Modern JS: prefer `Array.prototype.map`, `.filter()`, `.reduce()` for collection transforms
- Use `const` instead of `let` in `for...of` since the variable is re-assigned each iteration

---

### Pattern 3: Go for Loop

Go has only one loop construct: `for`. Its flexibility comes from `range` over slices, maps, channels, and strings.

```go
package main

import "fmt"

// ✅ GOOD — range over slice with index and value
func printItems(items []string) {
    for i, item := range items {
        fmt.Printf("%d: %s\n", i, item)
    }
}


// ✅ GOOD — ignore unused index variable with blank identifier
func sumValues(numbers []int) int {
    total := 0
    for _, val := range numbers {    // '_' discards the index
        total += val
    }
    return total
}


// ✅ GOOD — range over map (key-value pairs)
func printMapEntries(entries map[string]int) {
    for key, value := range entries {
        fmt.Printf("%s = %d\n", key, value)
    }
}


// ✅ GOOD — C-style for loop when you need custom control (step, reverse iteration)
func doubleEvenIndices(numbers []int) []int {
    result := make([]int, len(numbers))
    for i := 0; i < len(numbers); i += 2 {  // Step of 2
        result[i] = numbers[i] * 2
    }
    return result
}


// ✅ GOOD — range over channel (reads until closed)
func sumFromChannel(ch <-chan int) int {
    total := 0
    for val := range ch {       // Reads until channel is closed
        total += val
    }
    return total
}


// ❌ BAD — using index-based access when range provides values directly
func bad_printItems(items []string) {
    for i := 0; i < len(items); i++ {      // Unnecessary manual indexing
        fmt.Println(items[i])               // Double lookup instead of direct value
    }
}


// ❌ BAD — not handling empty slice gracefully
func bad_maxValue(numbers []int) int {
    maxVal := numbers[0]                    // Panic on empty slice!
    for _, val := range numbers {
        if val > maxVal {
            maxVal = val
        }
    }
    return maxVal
}


// ✅ GOOD — handle empty input defensively
func safeMaxValue(numbers []int) (int, error) {
    if len(numbers) == 0 {
        return 0, fmt.Errorf("cannot find max of empty slice")
    }
    maxVal := numbers[0]
    for _, val := range numbers[1:] {
        if val > maxVal {
            maxVal = val
        }
    }
    return maxVal, nil
}
```

**Key Go principles:**
- Go's `for` is the only loop construct — no `while`, `do...while`, or `foreach`
- `for i, v := range slice` gives you both index and value; use `_` to discard unused ones
- `for range ch` reads from a channel until it closes
- C-style `for` is idiomatic when you need custom increment steps (e.g., iterating by 2, reversing)
- Always handle empty slices before accessing by index to avoid panics

---

### Pattern 4: C/C++ for Loop

C uses the classic C-style loop exclusively. C++ adds range-based for loops and STL iterators.

```cpp
// === C-Style (C and C++) ===

// ✅ GOOD — traditional index-based loop with bounds checking
void print_array(const int* arr, size_t len) {
    if (arr == nullptr || len == 0) {
        return;  // Guard against null/empty
    }
    for (size_t i = 0; i < len; ++i) {   // Use size_t to avoid signedness warnings
        printf("%d\n", arr[i]);
    }
}


// ✅ GOOD — reverse iteration (common pattern in C/C++)
void process_in_reverse(const double* data, size_t len) {
    for (size_t i = len; i > 0; --i) {   // Post-decrement stops at 0 safely
        printf("%.2f\n", data[i - 1]);   // Adjust index since loop ends at 1
    }
}


// === C++ with STL Containers ===

#include <vector>
#include <string>
#include <map>
#include <algorithm>

// ✅ GOOD — range-based for (C++11+) iterates over values directly
void print_names(const std::vector<std::string>& names) {
    for (const auto& name : names) {     // const ref avoids copy
        std::cout << name << '\n';
    }
}


// ✅ GOOD — range-based for with structured binding (C++17+)
void print_scores(const std::map<std::string, int>& scores) {
    for (const auto& [name, score] : scores) {  // Structured binding
        std::cout << name << ": " << score << '\n';
    }
}


// ✅ GOOD — modifying elements requires mutable reference
void double_scores(std::vector<int>& scores) {
    for (int& score : scores) {           // Non-const ref allows modification
        score *= 2;
    }
}


// ✅ GOOD — iterator-based loop for STL containers (useful with erase-if pattern)
void remove_negatives(std::vector<int>& values) {
    auto it = values.begin();
    while (it != values.end()) {
        if (*it < 0) {
            it = values.erase(it);       // erase returns next valid iterator
        } else {
            ++it;
        }
    }
}


// ❌ BAD — using index on vector when range-based for suffices
void bad_print_vec(const std::vector<int>& vec) {
    for (size_t i = 0; i < vec.size(); ++i) {  // Unnecessary indexing
        std::cout << vec[i] << '\n';             // Random access overhead
    }
}


// ❌ BAD — erase during range-based for loop (iterator invalidation!)
void bad_remove_zeros(std::vector<int>& values) {
    for (auto val : values) {           // Copy, not iterator
        if (val == 0) {
            values.erase(/* ??? */);    // Cannot safely erase here — iterators invalidated
        }
    }
}


// ❌ BAD — signed/unsigned comparison warning
void bad_signed_loop(const std::vector<int>& data) {
    for (int i = 0; i < data.size(); ++i) {  // int vs size_t comparison!
        printf("%d\n", data[i]);
    }
}
```

**Key C/C++ principles:**
- In C, the `for` loop is your only iteration tool — manage bounds and increments manually
- Always use `size_t` for indices into arrays/vectors (avoid signed/unsigned mismatch)
- C++ range-based for loops (`for (const auto& x : container)`) are idiomatic and avoid manual indexing
- When erasing from a vector, use the iterator pattern (Erase–Remove Idiom or explicit iterator tracking) — never erase during a range-based loop
- Use `const auto&` in range-for to avoid copying large objects

---

### Pattern 5: Rust for Loop

Rust provides multiple iteration strategies with different ownership semantics. Choosing the right one prevents unnecessary allocations and borrow checker errors.

```rust
// ✅ GOOD — immutable iteration via .iter() (borrows elements)
fn print_items(items: &[String]) {
    for item in items.iter() {           // Borrows each element immutably
        println!("{item}");
    }
}


// ✅ GOOD — concise range-based iteration (Rust auto-delegates to .into_iter())
fn sum_numbers(numbers: &[i32]) -> i32 {
    let mut total = 0;
    for &num in numbers {                 // Destructures by value (i32 is Copy)
        total += num;
    }
    total
}


// ✅ GOOD — mutable iteration via .iter_mut()
fn double_values(values: &mut Vec<i32>) {
    for val in values.iter_mut() {       // Borrows each element mutably
        *val *= 2;
    }
}


// ✅ GOOD — enumerate (index + value) with proper destructuring
fn indexed_display(items: &[String]) -> Vec<(usize, &String)> {
    items.iter().enumerate().collect()   // Returns [(0, &"a"), (1, &"b"), ...]
}


// ✅ GOOD — zip combines two iterables side by side
fn add_vectors(a: &[f64], b: &[f64]) -> Vec<f64> {
    a.iter().zip(b.iter())
        .map(|(&x, &y)| x + y)
        .collect()
}


// ✅ GOOD — into_iter() consumes the collection (moves elements out)
fn consume_and_uppercase(list: Vec<String>) -> Vec<String> {
    let mut result = Vec::new();
    for mut s in list.into_iter() {       // Takes ownership of each String
        s.make_ascii_uppercase();         // Can mutate since we own the string
        result.push(s);
    }
    result
}


// ✅ GOOD — iterator chain with filter and map (functional style)
fn positive_squares(numbers: &[i32]) -> Vec<i32> {
    numbers.iter()
        .filter(|&&n| n > 0)             // Filter out negatives
        .map(|&n| n * n)                 // Square each remaining value
        .collect()                       // Collect into Vec<i32>
}


// ❌ BAD — indexing in a loop when iteration suffices (panics on out-of-bounds!)
fn bad_print_items(items: &[String]) {
    for i in 0..items.len() {             // Unsafe index access
        println!("{}", items[i]);         // Can panic if len == 0 and we access beyond
    }
}


// ❌ BAD — collecting into a new Vec inside an explicit loop when iterator methods exist
fn bad_double_values(values: &[i32]) -> Vec<i32> {
    let mut result = Vec::new();
    for i in 0..values.len() {            // Index-based with manual accumulation
        result.push(values[i] * 2);       // Unnecessary allocation overhead
    }
    result
}
```

**Key Rust principles:**
- `.iter()` — borrows elements immutably (most common, most flexible)
- `.iter_mut()` — borrows elements mutably (use when you need to modify in place)
- `.into_iter()` — takes ownership (move semantics), consumes the collection
- For primitive `Copy` types (`i32`, `f64`, bool), `for val in slice` automatically moves/copies values
- Use iterator adapters (`.map()`, `.filter()`, `.zip()`, `.enumerate()`) for composable, zero-cost-abstraction pipelines
- Indexing with `[i]` can panic; prefer iterators unless you specifically need random access

---

### Pattern 6: Shell Scripting for Loop

Shell `for` loops iterate over word-split lists. They handle files, variables, and command output. Understanding word splitting is critical for correctness.

```bash
#!/usr/bin/env bash
# Shell for-loop patterns — POSIX-compatible with bash extensions noted

# ✅ GOOD — iterating over a glob pattern (filenames)
function list_text_files() {
    local directory="${1:-.}"
    for filepath in "$directory"/*.txt; do     # Quotes protect spaces in filenames
        if [[ -f "$filepath" ]]; then           # Guard against empty glob (literal "*.txt")
            echo "Found: $filepath"
        fi
    done
}


# ✅ GOOD — iterating over a list of items (word-split safely)
function process_items() {
    local items=("alpha" "beta" "gamma")
    for item in "${items[@]}"; do              # Array expansion preserves each element
        echo "Processing: $item"
    done
}


# ✅ GOOD — iterating over command output (read line by line)
function count_lines_in_files() {
    local directory="${1:-.}"
    while IFS= read -r filepath; do            # Read avoids word splitting and trailing newlines
        if [[ -f "$filepath" ]]; then
            wc -l < "$filepath" | xargs echo "  Lines:" "$filepath"
        fi
    done < <(find "$directory" -maxdepth 1 -name "*.txt" -type f)
}


# ✅ GOOD — C-style arithmetic loop (bash-specific with double parentheses)
function sum_range() {
    local limit="${1:-10}"
    local total=0
    for (( i = 1; i <= limit; i++ )); do       # Integer arithmetic, no subshell needed
        (( total += i ))
    done
    echo "$total"
}


# ✅ GOOD — iterating over range in bash 4.0+ (brace expansion)
function print_numbers() {
    local start="${1:-1}"
    local end="${2:-5}"
    for i in $(seq "$start" "$end"); do        # seq generates the sequence
        echo "$i"
    done
}


# ❌ BAD — unquoted variable expansion (word splitting breaks on spaces!)
function bad_process_names() {
    local name="John Doe"
    for word in $name; do                      # Splits "John Doe" into "John" and "Doe"!
        echo "Name: $word"                     # Outputs two lines instead of one
    done
}


# ❌ BAD — iterating over glob without checking for no-match (literal pattern)
function bad_list_files() {
    for filepath in /nonexistent/*.xyz; do     # If no match, loop runs once with literal string
        echo "File: $filepath"                 # Outputs: "File: /nonexistent/*.xyz"
    done
}


# ✅ GOOD — handle no-match glob explicitly
function safe_list_files() {
    local pattern="/nonexistent/*.xyz"
    shopt -s nullglob                          # Disable literal expansion on no match
    for filepath in $pattern; do
        echo "File: $filepath"
    done
    shopt -u nullglob                          # Restore default behavior
}
```

**Key Shell principles:**
- Always quote variables in `for` loops: `"$var"` not `$var` — unquoted variables undergo word splitting and glob expansion
- Use `"${array[@]}"` to iterate over array elements without splitting
- Globs that match nothing expand to the literal pattern string — use `nullglob` or check file existence with `[[ -f ]]`
- C-style `for (( ))` is a bash extension, not POSIX; it handles integer arithmetic natively
- For command output, prefer `while read -r line` over `for line in $(command)` to handle spaces and newlines correctly

---

## Constraints

### MUST DO
- Always prefer iterating over values directly rather than using manual index counters when the index is not needed
- Guard against empty collections before accessing elements by index (add null/empty checks)
- Use language-native iterator constructs (enumerate, for...of, range, .iter(), "${array[@]}") instead of manual counter loops
- When erasing or removing elements from a collection during iteration, use the safe pattern: collect deletions first, then remove; or use the language's built-in filter/retain methods
- Use `const` / `final` / immutable bindings in loop variables when the value should not be reassigned
- Handle the no-match case for glob patterns in shell scripts (use `nullglob` or test existence)

### MUST NOT DO
- Modify a collection while iterating over it with a forward loop — this causes skipped elements, double-processing, or crashes depending on the language
- Use index-based loops to access every element when value iteration is available — it doubles the work (length calculation + indexing) and introduces off-by-one risks
- Use `for...in` on JavaScript arrays — it iterates over enumerable property keys (including inherited ones), not array values
- Access elements by index in Rust without bounds checking (`[i]` panics; use `.get(i)` for safe access or prefer iterators)
- Loop over `$(command_output)` in shell when filenames may contain spaces — use `while read -r` instead
- Use `range(len(...))` in Python when you only need values — it generates unnecessary integer objects and forces double lookup
- Ignore the return value of iterator operations that can fail (e.g., Rust's `.get()`, Go's map access with two-return syntax)

---

## Output Template

When helping with loop-related questions, produce output following this structure:

1. **Language Context** — Identify the programming language and version constraints (e.g., "Python 3.9+", "JavaScript ES6+"). State whether there are any compatibility concerns (e.g., bash `for (( ))` is not POSIX).

2. **Iteration Style** — Recommend the most idiomatic iteration pattern for the problem, explaining why it is preferred over alternatives. Include reasoning about readability, performance, and safety.

3. **Code Example** — Provide a complete, working code example using proper typing (where applicable), meaningful variable names, and docstrings/comments. Include both the primary pattern and the BAD anti-pattern to contrast.

4. **Edge Cases** — List notable gotchas specific to this language and pattern:
   - Empty collection behavior
   - Mutating during iteration safety
   - Off-by-one boundary conditions
   - Shell-specific concerns (word splitting, glob no-match)
   - Iterator invalidation risks (C++/Rust)

---

## Related Skills

| Skill | Purpose |
|---|---|
| `algorithms` | Algorithm complexity analysis for loop-heavy code |
| `sorting-algorithms` | Common sorting implementations use nested loops |

---

## Live References

> Authoritative documentation links for this skill's domain. The model follows markdown links to resolve external references and inline content.

- [Wikipedia — For Loop](https://en.wikipedia.org/wiki/For_loop)
- [Python for Loops — Official Docs](https://docs.python.org/3/tutorial/controlflow.html#for-statements)
- [Effective Python: Iteration Best Practices](https://realpython.com/python-for-loop/)
- [MDN Web Docs — JavaScript Looping](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration)
- [Go for Range Documentation](https://go.dev/ref/spec#For_statements)

