4 Loops and Repetition
A practical guide to designing, choosing, tracing, and debugging JavaScript loops and repetition patterns.
The structure of reliable repetition
A repeats a block of statements to process data, display patterns, validate input, or calculate a result. Reliable design has three connected parts:
establishes the starting state.
determines whether another should occur.
Progress or update changes the state so the can eventually become false.
For example, a initialized to can be tested against , used by the body, and then increased after each pass. After the value becomes , the is false and the ends.
When reviewing any , ask three questions: What is the initial state? What must be true for one more ? Which update moves the state toward stopping?
Takeaway: Repetition is safe only when the starting state, stopping test, and progress update work together.
Choosing the right
Choose the form according to what controls the repetition.
Use a
forfor a known or countable range. Its traditional sequence is once, test, body execution, update, and another test.Use a
whilewhen repetition depends on a or event and the number of passes is not known in advance. Because the is tested first, its body may execute zero times.Use a
do...whilewhen the body must execute before validation. This is useful for menus and input prompts because the is checked after the first attempt.
For arrays, use a when each value is needed but its index is not. Use an indexed when the position is required. In JavaScript, for...in is intended for enumerable property names of objects, whereas for...of iterates values from iterable objects such as arrays.
A useful selection rule is:
Identify whether the repeated set is a numeric range, a collection, or an unknown sequence.
Decide whether the number of repetitions is known.
Decide whether at least one execution is required.
Choose the form that expresses those facts most directly.
Takeaway: Match the to the source of repetition: countable range, collection values, -controlled work, or guaranteed first execution.
Core patterns
Many algorithms fit a small number of patterns.
Counting events
A records a specific event rather than merely counting passes. For example, while examining values from through , a can increase only when a value satisfies the evenness test. The -control variable changes on every pass, but the changes only for matching values.
Counters may also move downward or use a step other than . A countdown from to can update by , provided the and update point in the same direction.
Combining values
An combines information across iterations. A sum normally starts at , and a product normally starts at . The must be initialized before the ; declaring or resetting it inside the erases the previous result.
For scores , , , and , the sum is , and the average is . The changes on every .
Searching
A examines values until a target is found or the data ends. A position initialized to can mean “not found.” When the target is found, avoids unnecessary remaining work.
Filtering
A keeps or processes only values that satisfy a . For example, from the temperatures , , , , and , retaining values at most produces , , , and . can skip the rest of an for a value that should not be processed.
Takeaway: Before writing a , identify whether it counts events, combines values, searches for a match, or filters data.
Control flow and nested loops
A statement exits the nearest enclosing immediately. A statement skips the remaining statements in the current and proceeds to the next one. Neither statement changes the meaning of the 's , so the surrounding control flow still needs to be checked.
These statements are useful but can hide progress problems. In a while , if an if branch executes before the progress update, the may revisit the same state forever. Put the update where every relevant path reaches it, or restructure the and body so progress is explicit.
Nested repetition adds another layer of control. In a , the inner completes all of its iterations for each outer- . If the outer runs about times and the inner also runs about times, the body may execute about times. A grid with three rows and four columns therefore executes its inner body times.
For pairwise comparisons, starting the inner index after the outer index allows each unordered pair to be considered once. For values , , and , the pairs are , , and , not both orders of each pair.
Takeaway: Track which each control statement affects, and estimate nested work by counting inner executions for every outer execution.
Termination and debugging infinite loops
A terminates when its becomes false or a control statement exits it. To verify termination, identify a and explain how its value moves toward the stopping .
For example, if a variable begins at and doubles while it is less than , the values move through , , , and so on until the fails. The update makes progress toward the boundary.
Common causes of nontermination include:
The is never updated.
The wrong variable is updated.
The update moves away from the boundary, such as incrementing when the must decrement.
The can never become false.
The is reset inside the .
skips an update in awhile.
An intentional never-ending event still needs a deliberate exit mechanism or management by the surrounding system. For ordinary loops, state the stopping rule in plain language before implementing it.
Takeaway: Every terminating needs a reachable stopping and a progress update that moves toward it.
Tracing, boundaries, and a design process
Tracing means recording important variables after each . Consider a total initialized to and updated by adding for , , , and :
After the first , the total is .
After the second, it is .
After the third, it is .
After the fourth, it is .
The update then makes , so the test fails.
Tracing is especially effective for finding an . A beginning at and continuing while visits , , , , and . A beginning at and continuing while visits , , , , and . Both execute five times, but the ranges differ. The first form is common for JavaScript array indexes because arrays begin at index .
For a new , use this process:
Describe one in plain language.
Identify the repeated set.
Choose the form.
Name counters, accumulators, indexes, flags, and current values.
Initialize state before the .
Write the stopping boundary precisely.
Check progress on every path, including paths with
if,, or early exits.Test an empty collection, one item, boundary values, and a nonmatching value.
Trace a small example by hand.
When finding a maximum, handle an empty array first. For a nonempty array, initialize the current largest value with the first element, inspect the remaining elements, and replace the current value whenever a larger one appears. This avoids using a nonexistent element as the initial result.
Takeaway: Trace state changes and test boundaries before trusting a ’s output.