Skip to content
VLSI Mentor

Tcl Part I · Module 9 · Loops and Iteration

break and continue

Two commands that change how a loop runs: one abandons it, the other abandons just the current pass. They are simple, and the interesting part is not the mechanism but what you do with it. A loop body that filters out several uninteresting cases before doing its real work can be written as nested conditions wrapping the work, or as a short run of guards that skip onward — the same choice Chapter 8.3 made for conditions, and the same answer. This chapter also pins down one behaviour that differs between loop types and has hung more scripts than any other single mistake.

Foundation10 min readTclPart ILoopsbreakcontinue

Part I · Module 9 · Chapter 9.6 · break and continue

1. The two commands

break abandons the loop entirely:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach x {1 2 3 4 5} {
    if {$x == 3} break
    puts $x
}
puts "done"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1
2
done

continue abandons only the current pass:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach x {1 2 3 4 5} {
    if {$x % 2 == 0} continue
    puts $x
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1
3
5

Both work in foreach, for and while:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for {set i 0} {$i < 10} {incr i} {
    if {$i == 3} break
}
puts "for stopped at i=$i"

set n 0
while {1} {
    incr n
    if {$n >= 4} break
}
puts "while stopped at n=$n"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for stopped at i=3
while stopped at n=4

2. continue behaves differently in for and while

This is the one to internalise. In a for, the next script runs even when the body hits continue:

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, i = $i"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
seen = 0 1 3 4, i = 5

The loop skipped 2 and still advanced and still finished, because incr i lives in for's own argument rather than in the body.

Write the same logic as a while with the increment at the bottom and it hangs:

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

continue jumps straight back to the test, incr i is never reached, and i stays at 2 forever.

3. continue as a guard

Here is the readability argument. A loop that must ignore several kinds of line can be written with the work nested inside the tests:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set lines {"# a comment" "" "width = 32" "   " "depth = 16" "# end"}

foreach line $lines {
    if {[string trim $line] ne ""} {
        if {![string match "#*" [string trim $line]]} {
            if {[string match "*=*" $line]} {
                puts "setting: [string trim $line]"
            }
        }
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
setting: width = 32
setting: depth = 16

Or with guards that skip onward, leaving the work at the bottom and unindented:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach line $lines {
    set t [string trim $line]
    if {$t eq ""}                 continue
    if {[string match "#*" $t]}   continue
    if {![string match "*=*" $t]} continue

    puts "setting: $t"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
setting: width = 32
setting: depth = 16

Same output. The second version puts each rejection next to its reason, computes string trim once instead of three times, and ends with the real work on one unindented line.

break is at its clearest when you are looking for something and the rest of the list stops mattering once you find it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set ports {clk rst data valid ready}
set want data

set found ""
foreach p $ports {
    if {$p eq $want} {
        set found $p
        break
    }
}

puts [expr {$found eq "" ? "not found" : "found $found"}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
found data

Inside a procedure, return is usually better still, because it says "we are finished" rather than "stop looping and then fall through to whatever is next":

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc find_port {ports want} {
    foreach p $ports {
        if {$p eq $want} { return $p }
    }
    return ""
}

puts "[find_port {clk rst data} data] / [find_port {clk rst} data]<- empty"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
data / <- empty

5. They affect only the innermost loop

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set table {{1 2} {3 4} {5 6}}

foreach row $table {
    foreach cell $row {
        if {$cell == 3} break
        puts -nonewline "$cell "
    }
}
puts ""
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 2 5 6

The break ended the inner loop for the row containing 3, and the outer loop carried straight on to the next row. That is why 5 and 6 still appear. Chapter 9.10 deals with leaving an outer loop.

6. break inside a switch breaks the loop

Worth checking if you come from C, where break is how you end a switch case:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach x {a b c} {
    switch -- $x {
        a { puts "saw a" }
        b { break }
        c { puts "saw c" }
    }
}
puts "after the loop"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
saw a
after the loop

The break ended the loop, not the switch. Tcl's switch never falls through (Chapter 8.4 §2), so it has no use for break and does not absorb it. c was never reached.

7. A worked example

Scanning a log until a terminator, skipping noise, and reporting:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set log {
    "INFO starting"
    "# comment"
    "WARN clock skew"
    ""
    "ERROR missing cell"
    "INFO continuing"
    "END"
    "INFO never reached"
}

set problems 0
set scanned 0

foreach line $log {
    set t [string trim $line]

    if {$t eq ""}               continue
    if {[string match "#*" $t]} continue
    if {$t eq "END"}            break

    incr scanned
    if {[string match "WARN*" $t] || [string match "ERROR*" $t]} {
        incr problems
        puts "  $t"
    }
}

puts "scanned $scanned lines, $problems problems"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  WARN clock skew
  ERROR missing cell
scanned 4 lines, 2 problems

Two guards, one terminator, and the body's real work is three lines at the bottom.

8. Exercises

Exercise 1. What does each print?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach x {1 2 3} { if {$x == 2} break    ; puts $x }
foreach x {1 2 3} { if {$x == 2} continue ; puts $x }

Exercise 2. Rewrite with guards:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach v $values {
    if {$v ne ""} {
        if {[string is integer -strict $v]} {
            puts $v
        }
    }
}

Exercise 3. Why does continue hang a while that increments at the bottom but not a for?

Exercise 4. Predict the output and explain:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach x {a b c} {
    switch -- $x { a {puts A} b {break} c {puts C} }
}

Exercise 5. Write a loop finding the first element of {4 9 16 25} greater than 10, using break. Then rewrite it as a procedure using return.

Exercise 6. In a nested pair of loops, a break in the inner loop does not end the outer one. Give one way to end both.

Answers

1. The first prints 1. The second prints 1 then 3.

2.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach v $values {
    if {$v eq ""}                          continue
    if {![string is integer -strict $v]}   continue
    puts $v
}

3. In a for, the advance lives in the loop's own next-script argument, which runs even when the body hits continue. In a while, the advance is just another command in the body, so continue jumps back to the test before reaching it and the variable never changes.

4. It prints A and nothing else. The break in the b case ends the enclosing foreach, not the switch — Tcl's switch has no fallthrough and so no use for break. The c pass never happens.

5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set found ""
foreach n {4 9 16 25} {
    if {$n > 10} { set found $n ; break }
}
puts $found

proc first_over {list limit} {
    foreach n $list {
        if {$n > $limit} { return $n }
    }
    return ""
}
puts [first_over {4 9 16 25} 10]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
16
16

6. Set a flag in the inner loop and test it at the top of the outer one, or — usually better — move the nested loops into a procedure and return from the inner one, which leaves both at once.

9. Remember

  • break abandons the loop; continue abandons the current pass. Both work in foreach, for and while.
  • continue in a for still runs the next script, so the loop advances.
  • continue in a while does not run anything at the bottom of the body — advance before the continue, or use for. This is the commonest cause of a hung script.
  • Use continue as a guard to keep loop bodies flat: reject the uninteresting cases first, leave the real work unindented at the bottom.
  • That is the loop version of Chapter 8.3's guard clause.
  • Inside a procedure, prefer return to break when finding something ends the work.
  • They affect only the innermost loop.
  • break inside a switch ends the enclosing loop, not the switch — Tcl's switch never falls through, so it has no use for it.

Where this fits

Part of the Tcl curriculum.