Skip to content
VLSI Mentor

Tcl Part I · Module 9 · Loops and Iteration

for: Index-Driven Loops

Chapter 9.1 argued that foreach should be your default and that reaching for an index by reflex costs you four extra things to get wrong. This chapter is about the cases where the index genuinely is the job — numbering output, stepping by something other than one, walking a range that has no list behind it, or comparing an element with its neighbour. Tcl's for handles all of those, and it is worth seeing clearly that it is not special syntax: it is a command taking four arguments, three of which are scripts and one of which is an expression, which explains both what you can put in them and why they are braced.

Foundation10 min readTclPart ILoopsforIndexes

Part I · Module 9 · Chapter 9.4 · for: Index-Driven Loops

1. The four arguments

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for {set i 0} {$i < 4} {incr i} {
    puts "bit $i"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bit 0
bit 1
bit 2
bit 3

In order: a start script, a test expression, a next script, and a body script. Tcl runs the start once, then repeatedly evaluates the test, runs the body, and runs the next script.

None of that is syntax. for is a command, like if in Chapter 8.1, taking four ordinary arguments — which is why there are no semicolons or parentheses, and why all four are braced.

2. The start and next are scripts

Because they are scripts rather than single statements, they can contain several commands separated by semicolons (Chapter 2.10):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for {set i 0 ; set j 10} {$i < $j} {incr i 3 ; incr j -3} {
    puts "i=$i j=$j"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
i=0 j=10
i=3 j=7

Two counters converging. You will not need this often, and it is worth knowing that nothing special is happening — they are just scripts.

The start script can also be empty when the variable is already set:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set i 0
for {} {$i < 3} {incr i} {
    puts $i
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0
1
2

3. The test is an expression, and it is braced

Everything Chapter 8.2 said about if applies here. The test is handed to expr, and bracing it means the variable is read fresh on every pass rather than substituted once while the line is parsed.

Getting that wrong does not produce an error — it produces a loop that never ends. That is important enough to have its own chapter: Chapter 9.7.

4. When for earns its place

Stepping by something other than one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for {set i 0} {$i < 32} {incr i 8} {
    puts "byte boundary at $i"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
byte boundary at 0
byte boundary at 8
byte boundary at 16
byte boundary at 24

Counting down:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for {set i 3} {$i >= 0} {incr i -1} {
    puts "bit $i"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bit 3
bit 2
bit 1
bit 0

A range with no list behind it — there is nothing to foreach over:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set total 0
for {set i 1} {$i <= 100} {incr i} {
    incr total $i
}
puts "sum 1..100 = $total"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sum 1..100 = 5050

Comparing an element with its neighbour, which needs two positions at once:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set readings {3 5 4 9 9 2}

for {set i 1} {$i < [llength $readings]} {incr i} {
    set prev [lindex $readings [expr {$i - 1}]]
    set cur  [lindex $readings $i]
    if {$cur > $prev} {
        puts "rose at index $i: $prev -> $cur"
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rose at index 1: 3 -> 5
rose at index 3: 4 -> 9

5. for returns nothing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set r [for {set i 0} {$i < 2} {incr i} {expr {$i}}]
puts "'[set r]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
''

Same as foreach. Loops are run for their effect; collecting results is Chapter 9.9.

6. continue still runs the next script

This is the detail worth taking away. Inside a for, continue skips the rest of the body — but the next script still runs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set seen {}
for {set i 0} {$i < 5} {incr i} {
    if {$i == 2} continue
    lappend seen $i
}
puts "seen = $seen"
puts "i ended at $i"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
seen = 0 1 3 4
i ended at 5

2 was skipped and the loop still advanced and still finished. Now the same logic written as a hand-rolled while:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set i 0
set seen {}
while {$i < 5} {
    if {$i == 2} continue    ;# <- skips the incr below: never ends
    lappend seen $i
    incr i
}

That loop hangs. continue jumps back to the test, the increment at the bottom of the body never runs, and i stays at 2 forever.

7. A worked example

Formatting a list into fixed-width columns, where the index really is the point:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set items {clk rst din dout en valid ready ack}
set percol 3

for {set i 0} {$i < [llength $items]} {incr i $percol} {
    set row {}
    for {set j 0} {$j < $percol} {incr j} {
        set idx [expr {$i + $j}]
        if {$idx < [llength $items]} {
            lappend row [format "%-8s" [lindex $items $idx]]
        }
    }
    puts "  [join $row]"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  clk      rst      din     
  dout     en       valid   
  ready    ack

The outer loop steps by three, the inner one fills a row, and the if stops the last row running off the end. Neither loop could be a foreach, because both use the index arithmetically.

8. Exercises

Exercise 1. Name the four arguments of for and say which one is an expression rather than a script.

Exercise 2. Write a loop printing the even numbers from 0 to 10 inclusive.

Exercise 3. Why does this run forever, and what is the minimal fix?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set i 0
while {$i < 5} {
    if {$i == 2} continue
    incr i
}

Exercise 4. Rewrite as a foreach and say why that is better:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for {set i 0} {$i < [llength $l]} {incr i} { puts [lindex $l $i] }

Exercise 5. Write a loop over {4 7 2 9} printing each value and whether it is larger than the one before it.

Exercise 6. Give two cases where for is the right choice and foreach cannot do the job.

Answers

1. Start script, test expression, next script, body script. The test is the expression; the other three are scripts.

2.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for {set i 0} {$i <= 10} {incr i 2} {
    puts $i
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0
2
4
6
8
10

3. When i reaches 2, continue jumps straight back to the test without reaching incr i, so i stays at 2 and the condition stays true forever. The minimal fix is to advance before the continue can be reached — or to use for, which keeps the increment in its own argument:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for {set i 0} {$i < 5} {incr i} {
    if {$i == 2} continue
}

4.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach item $l { puts $item }

The index was used only to fetch the element, so it was never needed. Removing it removes the bound, the increment, and the out-of-range lindex that would silently yield an empty string.

5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set readings {4 7 2 9}
for {set i 0} {$i < [llength $readings]} {incr i} {
    set cur [lindex $readings $i]
    if {$i == 0} {
        puts "$cur (first)"
    } else {
        set prev [lindex $readings [expr {$i - 1}]]
        puts "$cur ([expr {$cur > $prev ? "up" : "down"}])"
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
4 (first)
7 (up)
2 (down)
9 (up)

6. Any two of: stepping by something other than one; counting down; iterating a numeric range with no list behind it; needing two positions at once to compare neighbours; using the index itself in the output.

9. Remember

  • for {start} {test} {next} {body} — four arguments, three scripts and one expression. It is a command, not syntax.
  • Start and next are scripts, so they can hold several commands separated by ;, and start may be empty.
  • The test is braced for the same reasons an if condition is — and getting it wrong here causes an infinite loop rather than an error.
  • Use for when the index is the job: a step other than one, counting down, a numeric range, or comparing neighbours.
  • If the body only uses the index for lindex, you wanted foreach.
  • for returns the empty string.
  • continue inside a for still runs the next script, so the loop still advances.
  • A hand-rolled while that increments at the bottom hangs on continue — that is the strongest reason to prefer for.

Where this fits

Part of the Tcl curriculum.