Programming Fundamentals and Object-Oriented Programming

Chapter 3
Control Flow and Structured Programming

Post Graduate Diploma in Full Stack Development
Programming Fundamentals and Object-Oriented Programming   •   Post Graduate

3. Control Flow and Structured Programming

Introduction and Context

A 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 Objectives

After 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 Execution

The 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 Decisions

Selection is used when execution depends on a Boolean expression. A Boolean expression evaluates to either `true` or `false`.

The `if` Structure

An `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 Selection

An `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 Selection

An `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 Clauses

A 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 Evaluation

Complex 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 Precisely

Iteration 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 Loops

A `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 Loops

A `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 Logic

A 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 Thinking

Many 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 Flow

Tracing 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 Platform

Background

This 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 Problem

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

Analysis

The 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 Solution

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

Outcome

The 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 Points

The 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 Considerations

Typical 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 Flow

Prefer 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 Developments

Modern 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 Summary

Control 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 Terms

Control 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 Questions

Short Answer Questions

1. 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 Questions

1. 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 Questions

1. 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 Answers

Question 1

Which 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 2

What 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 3

Why 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 4

Which 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 5

What 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 6

A 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 7

Which 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 8

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

Which 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 10

In 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

Programming Fundamentals and Object-Oriented Programming
Chapter 3 of 15