3. Control Flow and Structured ProgrammingIntroduction and ContextA program is not merely a collection of statements; it is an organised process for transforming inputs into outputs. The order in which statements execute determines what the program means. In simple programs, statements may execute from top to bottom. Real applications, however, must make decisions, repeat operations, respond to changing conditions, and terminate safely. These requirements are addressed through control flow. Control flow describes the order in which instructions are executed. Structured programming provides disciplined ways to express that order through sequence, selection, iteration, and carefully controlled nesting. These ideas are central to every programming language, including Java, C#, Python, JavaScript, and TypeScript. Although syntax differs, the underlying reasoning remains remarkably consistent. For a full-stack developer, control flow appears in form validation, authentication, shopping-cart calculations, database processing, API response handling, user-interface state management, and server-side business rules. Poorly designed control flow can produce security defects, infinite loops, incorrect transactions, inaccessible code, and difficult maintenance. Consequently, the ability to trace and design control flow is both a foundational academic skill and a professional engineering competency. Learning ObjectivesAfter studying this chapter, the learner should be able to: 1. Explain control flow and the principles of structured programming. 2. Distinguish sequence, selection, iteration, and state-based logic. 3. Select suitable conditional and looping structures for a problem. 4. Apply nesting and short-circuit evaluation safely. 5. Trace program execution using variables, conditions, and loop states. 6. Identify common control-flow defects, including infinite loops and unreachable code. 7. Design readable, testable, and maintainable control logic for software applications. The Logic of Program ExecutionThe simplest control structure is sequence. Statements execute in the order in which they appear. For example: ```text subtotal = price * quantity tax = subtotal * taxRate total = subtotal + tax ``` The second statement depends on the result of the first, and the third depends on the second. A sequence is appropriate when every operation must occur once and in a fixed order. A useful way to understand execution is to maintain a state table. The state of a program consists of the values of its variables, the current instruction, and sometimes external conditions such as a user session or database result. Consider: ```text x = 4 x = x + 3 x = x * 2 ``` The value of `x` changes from 4 to 7 and then to 14. Tracing therefore means recording relevant state changes rather than reading code passively. Control flow extends sequence in three principal ways: - Selection chooses one path among alternatives.
- Iteration repeats a block while a condition or collection requires it.
- Transfer or termination changes the normal movement through a structure, for example through `break`, `continue`, `return`, or an exception.
Structured programming seeks to express algorithms using clear combinations of sequence, selection, and iteration. Unrestricted jumps, historically implemented with `goto`, can make execution difficult to predict. Modern programming generally favours block structures with explicit entry and exit points. Selection: Expressing DecisionsSelection is used when execution depends on a Boolean expression. A Boolean expression evaluates to either `true` or `false`. The `if` StructureAn `if` statement executes a block only when its condition is true: ```text if (balance >= withdrawal) { balance = balance - withdrawal } ``` If the condition is false, the block is skipped. The condition should express a meaningful business rule, and the block should represent one coherent action. Two-Way SelectionAn `if-else` structure chooses between two alternatives: ```text if (mark >= 50) { result = "Pass" } else { result = "Fail" } ``` The alternatives should be mutually exclusive and collectively meaningful. A frequent error is the use of assignment instead of comparison, such as `if (x = 5)`, in languages where that syntax is accepted. Such errors can alter program state and produce misleading results. Multiway SelectionAn `else-if` chain is suitable when conditions are ordered and ranges are being tested: ```text if (score >= 80) { grade = "A" } else if (score >= 70) { grade = "B" } else if (score >= 60) { grade = "C" } else { grade = "D" } ``` Order matters. Since the first true branch is selected, the most restrictive or highest-priority condition should generally appear first. If `score >= 60` appeared first, all scores of 60 or above would bypass the higher grades. A `switch` or equivalent match expression is often clearer when one value is compared with several discrete alternatives: ```text switch (role) { case "admin": showAdminPanel(); break case "editor": showEditorPanel(); break case "viewer": showReadOnlyPanel(); break default: showError(); } ``` A switch structure is not automatically superior to an `if` chain. It is most appropriate for discrete, readily identifiable cases. Range comparisons, compound conditions, and highly variable predicates are usually clearer with `if-else` logic. In languages that support modern pattern matching, a match expression can combine readable alternatives with type or property conditions. Guard ClausesA guard clause handles an exceptional or invalid condition early and returns from a function: ```text if (user == null) return error("User is required") if (!user.isActive) return error("Account is inactive") return createSession(user) ``` Guard clauses reduce deep nesting and make the normal path easier to see. They should not be used to conceal complex control flow; each guard should communicate a clear precondition or early-exit rule. Boolean Logic and Short-Circuit EvaluationComplex decisions are built from Boolean operators. In common notation, `&&` means logical AND, `||` means logical OR, and `!` means NOT. The expression `A && B` is true only when both operands are true; `A || B` is true when at least one operand is true. Many languages use short-circuit evaluation. With AND, the second operand is evaluated only if the first is true. With OR, the second is evaluated only if the first is false: ```text if (account != null && account.isVerified()) { allowAccess() } ``` If `account != null` is false, the second expression is not evaluated, preventing an attempt to call a method on a null reference. Similarly: ```text if (cache.contains(key) || loadFromDatabase(key)) { process(key) } ``` The database is queried only when the cache does not contain the key. Short-circuiting is both a performance technique and a correctness mechanism. However, it becomes dangerous when the skipped operand contains an essential side effect: ```text ready || initialiseSystem() ``` If `ready` is true, initialisation never occurs. Conditions should preferably be free of hidden side effects. When an operation must always happen, write it as a separate statement. Operator precedence can also cause errors. Parentheses clarify intention: ```text if ((isAdmin || isManager) && accountActive) { grantAccess() } ``` Without parentheses, readers may misunderstand the policy even when the language interprets it correctly. Iteration: Repeating Work PreciselyIteration is appropriate when an operation must be performed repeatedly. Every sound loop requires three conceptual elements: 1. Initialisation establishes the starting state. 2. Continuation condition determines whether another repetition is permitted. 3. Progress or update moves the state toward termination. Omitting the update can create an infinite loop. Using an incorrect boundary can omit or duplicate an item. Count-Controlled LoopsA `for` loop is appropriate when the number of repetitions is known or naturally expressed by an index: ```text for (i = 0; i < items.length; i = i + 1) { total = total + items[i].price } ``` The loop processes indices 0 through `items.length - 1`. The condition uses `<` because the upper boundary is excluded. Confusing `<` and `<=` is a common off-by-one error. Collection-oriented loops are often preferable when the index is not needed: ```text for (item of items) { total = total + item.price } ``` They communicate intent and avoid accidental index errors. Condition-Controlled LoopsA `while` loop is suitable when repetition depends on a condition and the number of iterations is not known in advance: ```text while (queue is not empty) { request = queue.removeFront() process(request) } ``` A `while` loop may execute zero times if its condition is initially false. A `do-while` loop evaluates its condition after the body, so it executes at least once: ```text do { choice = readMenuChoice() } while (choice is invalid) ``` This is useful for input menus and retry operations, although retry limits should be considered where external services or user input are involved. Loop Control Statements`break` exits the nearest loop or switch. It is appropriate when the desired item has been found or a terminating condition has been reached. `continue` skips the remainder of the current iteration and begins the next one. Both can be useful, but excessive use may obscure the loop’s main condition. `return` exits the current function, possibly from inside a loop. Exceptions transfer control to error-handling mechanisms and should represent exceptional conditions rather than ordinary branching. Nesting and the Design of Structured LogicA nested structure is placed inside another structure. For example, a loop may contain an `if` statement, or a conditional branch may contain another loop. Nesting is often necessary: ```text for (order of orders) { if (order.status == "pending") { for (line of order.lines) { reserveStock(line.productId, line.quantity) } } } ``` The example checks every order, selects pending orders, and processes each line. Its logic is understandable because each level has a distinct responsibility. Deep nesting, however, increases cognitive load. It can indicate that a function is performing too many tasks. Techniques for controlling complexity include guard clauses, extracting helper functions, using collection operations carefully, and simplifying Boolean expressions. A useful professional question is not merely “Can this logic be written?” but “Can another developer verify its correctness quickly?” Nested loops also affect performance. If an outer loop processes `n` records and an inner loop scans `m` records for each one, the work may be proportional to `n × m`. Replacing repeated searches with a map or set can improve both clarity and efficiency. State-Based Logic and Finite-State ThinkingMany applications behave differently depending on their current state. A state is a meaningful condition of an object or process, such as `Draft`, `Submitted`, `Approved`, or `Cancelled`. State-based logic becomes safer when valid transitions are made explicit. Consider an order: ```text if (order.state == "Draft" && paymentReceived) { order.state = "Submitted" } else if (order.state == "Submitted" && managerApproved) { order.state = "Approved" } ``` The code expresses transitions, but a larger system may benefit from a finite-state machine. A finite-state machine defines a finite set of states, permitted events, and transitions. For example, an order might follow: `Draft → Submitted → Approved → Fulfilled` with cancellation allowed only from selected states. This model prevents invalid transitions such as moving directly from `Draft` to `Fulfilled`. State-based design is especially relevant to web applications, authentication sessions, payment workflows, background jobs, and user-interface components. The state should have a single authoritative representation, and transitions should be validated at the boundary where they occur. In distributed systems, state changes may also require transactional safeguards, idempotency, and concurrency control. Tracing and Verifying Control FlowTracing is a systematic method for determining what a program does. Begin by identifying inputs and initial values. Then record each condition, selected branch, loop iteration, and state update. A trace table may contain columns such as iteration number, variable values, condition result, and output. Consider: ```text value = 1 for (i = 1; i <= 3; i = i + 1) { value = value * i } ``` The initial value is 1. At `i = 1`, value becomes 1; at `i = 2`, it becomes 2; at `i = 3`, it becomes 6. When `i` becomes 4, the condition is false and the loop ends. Tracing exposes the loop’s boundary and confirms that it executes three times. For more complex programs, trace only variables relevant to the question. Assertions can formalise expectations: ```text assert(total >= 0) ``` A loop invariant is a property that remains true before and after each iteration. For a running total of non-negative prices, an invariant might be that the total equals the sum of all processed prices and is never negative. Invariants support reasoning about correctness, especially in algorithms and safety-critical code. An Illustrative Case: Controlling Access to an Online Learning PlatformBackgroundThis illustrative case concerns a university learning platform with roles for students, instructors, and administrators. Users may be active or suspended, and course resources may be public, enrolled-only, or instructor-only. Situation and ProblemAn initial implementation used a long sequence of nested conditions. Suspended users occasionally reached resource pages, instructors could not access some of their own materials, and a missing enrolment record caused a runtime error. AnalysisThe access decision combined three concerns: authentication, account status, and resource policy. These concerns were evaluated inconsistently across different routes. Some code also performed database lookups before checking whether the user was authenticated. The design lacked a clear model for role and resource states. Approach and SolutionThe team introduced guard clauses: ```text if (user == null) return DENY if (user.status != "Active") return DENY ``` It then used short-circuit evaluation to prevent unsafe access to optional enrolment data. Resource policies were represented explicitly, and role checks were placed in a dedicated authorisation function. A state model distinguished `Published`, `Archived`, and `Draft` resources. Tests covered unauthenticated users, suspended users, enrolled students, instructors, and invalid resource states. OutcomeThe redesigned flow had fewer nesting levels and made the normal authorisation path visible. Invalid transitions and missing data were handled deliberately. Most importantly, the team could test each rule independently rather than relying on large end-to-end tests alone. Learning PointsThe case demonstrates that control flow is also a design and governance issue. Ordering checks safely, separating responsibilities, representing states explicitly, and testing boundary conditions reduce both defects and security risk. Common Defects, Risks, and Professional ConsiderationsTypical defects include off-by-one errors, reversed conditions, accidental fall-through in switch statements, infinite loops, duplicated branches, unreachable statements, and failure to handle empty or null input. A condition may also be logically correct but operationally unsafe if it exposes sensitive information through different error messages. Input validation should occur before data is used in business logic. Authorisation must be checked on the server, not merely hidden in a client interface. Control flow involving payments, identity, personal data, or regulated records should be auditable and tested against denial as well as success cases. Logging should provide enough information for diagnosis without exposing passwords, tokens, or unnecessary personal information. Automated tools can assist with control-flow quality. Linters identify unreachable code and suspicious conditions; static analysers detect null dereferences and some infinite-loop patterns; test coverage tools reveal untested branches. AI-assisted coding tools may suggest conditions or refactorings, but their output requires human review. A generated loop that appears plausible may still mishandle boundaries, side effects, concurrency, or authorisation rules. Best Practices for Structured Control FlowPrefer the simplest structure that accurately expresses the requirement. Name Boolean variables positively and clearly, such as `isAuthenticated` rather than ambiguous names. Place more specific conditions before general ones, use parentheses for compound logic, and avoid side effects inside conditions. Keep loop bodies focused, establish obvious progress toward termination, and test zero, one, maximum, and invalid cases. Separate policy decisions from mechanisms. For example, a function should determine whether access is allowed, while another function performs the page rendering. Represent important lifecycle states explicitly and reject invalid transitions. Use guard clauses to control exceptional paths, but preserve a readable main path. Finally, trace non-trivial logic manually and support it with automated unit, integration, and property-based tests where appropriate. Current DevelopmentsModern languages increasingly provide pattern matching, expression-based conditionals, immutable data, and declarative collection operations. These features can make control flow more expressive, but they do not remove the need to understand evaluation order and state changes. Reactive and event-driven systems introduce control flow across callbacks, promises, asynchronous tasks, and message handlers. In such systems, developers must reason about completion order, cancellation, retries, and duplicated events. The fundamental principles remain unchanged: make transitions explicit, preserve invariants, handle failure deliberately, and design execution paths that humans can inspect and test. Chapter SummaryControl flow determines the order in which a program executes. Sequence handles fixed operations; selection chooses among alternatives; iteration repeats work; and state-based logic models meaningful conditions and transitions. Structured programming combines these constructs in a disciplined manner and avoids unnecessarily obscure jumps. Short-circuit evaluation can prevent unsafe evaluation and reduce unnecessary work, but side effects inside conditions can create hidden defects. Loops require initialisation, a continuation condition, and progress toward termination. Nesting is useful when responsibilities are clear, but excessive nesting should be reduced through guard clauses and function extraction. Tracing, assertions, and invariants provide practical methods for verifying execution. In professional systems, control flow is closely connected with security, validation, authorisation, testing, maintainability, and automated analysis. Key TermsControl flow: The order in which a program’s instructions and blocks are executed. Structured programming: A programming approach based primarily on sequence, selection, and iteration with clear control boundaries. Sequence: Execution of statements in their written order. Selection: Choosing an execution path according to a Boolean condition. Boolean expression: An expression that evaluates to true or false. Iteration: Repeated execution of a block of statements. Guard clause: An early condition that exits a function when a precondition or exceptional case is detected. Short-circuit evaluation: Evaluation that stops once the result of a Boolean expression is determined. Nesting: Placing one control structure inside another. Loop invariant: A property that remains true throughout loop execution. Off-by-one error: A boundary mistake that causes one item to be omitted or processed extra times. Infinite loop: A loop that does not reach a terminating condition. State: A meaningful condition describing the status of an object or process. State transition: A permitted change from one state to another in response to an event. Finite-state machine: A model comprising defined states, events, and permitted transitions. Fall-through: Unintended continuation from one switch case into the next case. Trace table: A structured record of variable values and conditions during execution. Assertion: An executable statement expressing a condition expected to be true. Cyclomatic complexity: A measure related to the number of independent paths through control-flow logic. Review QuestionsShort Answer Questions1. What is the difference between selection and iteration? 2. Why is short-circuit evaluation useful when checking a possibly null object? 3. Identify the three conceptual elements required by a reliable loop. 4. What is a guard clause, and how can it improve readability? 5. Define a loop invariant. Long Answer Questions1. Explain the major control structures used in structured programming and provide a suitable application for each. 2. Discuss the design choices between an `if-else` chain, a switch structure, and a pattern-matching expression. 3. Explain how nesting affects readability, testing, and performance. 4. Describe finite-state-machine thinking and apply it to an order-processing workflow. 5. Explain a systematic method for tracing a loop and detecting an off-by-one error. Analytical / Application Questions1. Design the control flow for an API endpoint that accepts a request only when the user is authenticated, active, authorised for the resource, and within a request limit. Explain the order of checks. 2. Analyse a loop that processes an array from index 0 through `length`. Identify the likely defect and propose a correction. 3. A payment workflow permits `Pending`, `Paid`, `Refunded`, and `Failed` states. Specify valid and invalid transitions and explain how explicit state validation improves reliability. 22. Multiple Choice Questions with AnswersQuestion 1Which control structure is most suitable when a task must be repeated for every element in a collection and the index is not needed? A. Collection-oriented loop B. Switch statement C. Guard clause D. Nested conditional Correct Answer: A. Collection-oriented loop Question 2What happens in `account != null && account.isVerified()` when `account != null` is false? A. The second operand is evaluated and causes an error B. The entire expression is treated as true C. The second operand is skipped and the expression is false D. The program automatically retries the condition Correct Answer: C. The second operand is skipped and the expression is false Question 3Why should a higher grade boundary generally appear before a lower grade boundary in an else-if chain? A. Only the first true branch executes B. Lower boundaries cannot be compared numerically C. The compiler requires descending order D. The final else branch is always executed first Correct Answer: A. Only the first true branch executes Question 4Which situation is most appropriate for a do-while loop? A. Processing zero or more records from a queue B. Displaying a menu at least once and repeating it for invalid input C. Selecting one value from several fixed cases D. Executing a statement exactly once without a condition Correct Answer: B. Displaying a menu at least once and repeating it for invalid input Question 5What is the principal risk of placing `initialiseSystem()` in `ready || initialiseSystem()`? A. The system is initialised twice in every case B. Initialisation may be skipped when `ready` is true C. The expression can never become false D. The OR operator changes the value of `ready` Correct Answer: B. Initialisation may be skipped when `ready` is true Question 6A loop uses `i <= items.length` while accessing `items[i]` in a zero-based array. What defect is most likely? A. The first element is always skipped B. The loop cannot execute C. The last iteration may access an invalid index D. The array is automatically sorted Correct Answer: C. The last iteration may access an invalid index Question 7Which practice most directly reduces excessive nesting in a function? A. Adding more global variables B. Using guard clauses for invalid or exceptional cases C. Replacing every condition with a switch D. Removing all validation Correct Answer: B. Using guard clauses for invalid or exceptional cases Question 8What does a finite-state machine explicitly define? A. Only the variables used by a program B. Database tables and network protocols C. States, events, and permitted transitions D. The number of lines in each function Correct Answer: C. States, events, and permitted transitions Question 9Which statement best describes a loop invariant? A. A condition that is guaranteed to remain true during loop execution B. A command that exits every loop in a program C. A variable that must never be updated D. A condition evaluated only after the loop ends Correct Answer: A. A condition that is guaranteed to remain true during loop execution Question 10In the access-control case, why should authentication be checked before optional enrolment data is accessed? A. It guarantees that every user is enrolled B. It prevents unnecessary or unsafe access for unauthenticated users C. It makes the database return more records D. It eliminates the need for authorisation checks Correct Answer: B. It prevents unnecessary or unsafe access for unauthenticated users
|