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 11 can be tested against 55, used by the body, and then increased after each pass. After the value becomes 66, the count≤5\text{count} \leq 5 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 for for a known or countable range. Its traditional sequence is once, test, body execution, update, and another test.

  • Use a while when 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...while when 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:

  1. Identify whether the repeated set is a numeric range, a collection, or an unknown sequence.

  2. Decide whether the number of repetitions is known.

  3. Decide whether at least one execution is required.

  4. 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 11 through 2020, 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 11. A countdown from 1010 to 00 can update by −2-2, provided the and update point in the same direction.

Combining values

An combines information across iterations. A sum normally starts at 00, and a product normally starts at 11. The must be initialized before the ; declaring or resetting it inside the erases the previous result.

For scores 7272, 8585, 9191, and 6868, the sum is 316316, and the average is 316÷4=79316 \div 4 = 79. The changes on every .

Searching

A examines values until a target is found or the data ends. A position initialized to −1-1 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 6868, 9191, 7474, 102102, and 6565, retaining values at most 100100 produces 6868, 9191, 7474, and 6565. 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 nn times and the inner also runs about nn times, the body may execute about n2n^2 times. A grid with three rows and four columns therefore executes its inner body 3×4=123 \times 4 = 12 times.

For pairwise comparisons, starting the inner index after the outer index allows each unordered pair to be considered once. For values 22, 44, and 66, the pairs are (2,4)(2,4), (2,6)(2,6), and (4,6)(4,6), 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 11 and doubles while it is less than 100100, the values move through 11, 22, 44, 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 a while .

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 00 and updated by adding ii for i=1i=1, 22, 33, and 44:

  • After the first , the total is 11.

  • After the second, it is 33.

  • After the third, it is 66.

  • After the fourth, it is 1010.

The update then makes i=5i=5, so the test i≤4i \leq 4 fails.

Tracing is especially effective for finding an . A beginning at 00 and continuing while i<5i < 5 visits 00, 11, 22, 33, and 44. A beginning at 11 and continuing while i≤5i \leq 5 visits 11, 22, 33, 44, and 55. Both execute five times, but the ranges differ. The first form is common for JavaScript array indexes because arrays begin at index 00.

For a new , use this process:

  1. Describe one in plain language.

  2. Identify the repeated set.

  3. Choose the form.

  4. Name counters, accumulators, indexes, flags, and current values.

  5. Initialize state before the .

  6. Write the stopping boundary precisely.

  7. Check progress on every path, including paths with if, , or early exits.

  8. Test an empty collection, one item, boundary values, and a nonmatching value.

  9. 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.