The fact that governs every loop
An iRule runs inside TMM, and each TMM instance is single-threaded on its core. A loop does not run in the background; it runs inline, holding that connection's processing until it finishes. So the first thing to know about loops in iRules is not their syntax, it is their cost: iterate over too many items and you stall the TMM handling that request. F5's own guidance is blunt about it, warning that because the loops are blocking and TMM is single-threaded, you really do not want to iterate through too many cases before moving on. The classic mistake, a big counting loop used as a homemade "sleep," is exactly the thing not to do.
That does not make loops forbidden. It makes them something to keep small and bounded, and to prefer alternatives to when one exists.
foreach first
When you are walking a list, foreach is both faster and cleaner than a for or while loop that indexes the list by hand. F5's optimization notes make the same point: the for version that pulls each element with lindex is the slower one.
when HTTP_REQUEST {
set domains { bob.com ted.com example.com }
# preferred: foreach, faster and more elegant
foreach domain $domains {
# work with $domain
}
}
Reach for for or while when you genuinely need index arithmetic, a non-list counter, or a condition that is not just "each element." Both are available, and both give you continue (stop this pass, go to the next test) and break (leave the loop immediately).
set count [llength $domains]
# slower than foreach: manual indexing
for { set i 0 } { $i < $count } { incr i } {
set domain [lindex $domains $i]
}
# while, equivalent and equally slower for this job
set i 0
while { $i < $count } {
set domain [lindex $domains $i]
incr i
}
The list commands you actually have
iRules expose most of Tcl's list handling. Available: list, split, join, concat, llength, lindex, lrange, linsert, lreplace, lsort, lsearch, lappend, and lset. A few Tcl commands are not available in iRules, notably lrepeat, lreverse, and dict, so do not reach for those.
Two building blocks do most of the real work. split turns a string into a proper list on a separator, and lindex pulls an element out by position (starting at zero). Together they replace a lot of manual parsing:
# take the first field of a "!"-separated cookie value
set id [lindex [split [HTTP::cookie "JSESSIONID"] "!"] 0]
A note on iRule commands versus Tcl commands
Some parsing has two spellings: a pure-Tcl one and an iRule-command one. getfield does much the same job as lindex [split ...] and reads more clearly, but there is a subtlety worth knowing. An iRule-specific command has to jump from the Tcl virtual machine back into TMM to run, which the pure-Tcl commands do not. F5 describes the cost as incredibly negligible in normal use, something to be aware of only if you are squeezing out every last cycle. Note too that they count differently: getfield starts at field 1, while lindex starts at index 0.
# same result, two spellings
set id [lindex [split [HTTP::cookie "JSESSIONID"] "!"] 0] ;# 0-indexed
set id [getfield [HTTP::cookie "JSESSIONID"] "!" 1] ;# 1-indexed
Prefer a lookup to a loop
The through-line from the rest of this cluster applies here too. If a loop is scanning a list to find a match, a data group read with class match or a directly-indexed static:: array will usually do the same work without the per-item iteration, and without the blocking cost that grows with the list. The comparison of if, switch, class, and static arrays covers when each of those fits. When you do need a loop, keep the body small, cap the number of iterations, and if you want to know what it actually costs on your platform, measure it with timing statistics through the iRules runtime calculator. And remember the rule from the static:: namespace article: whatever the loop touches, a global variable inside it still demotes the virtual to a single core.