COBOLMainframe

COBOL Control Flow: IF, EVALUATE, and PERFORM Explained

TT
TopicTrick
COBOL Control Flow: IF, EVALUATE, and PERFORM Explained

Control flow determines how COBOL programs make decisions and repeat work. Three statements handle the vast majority of COBOL logic: IF for conditional branching, EVALUATE for multi-way dispatch, and PERFORM for iteration and modular calls. Understanding all three — and their variants — is essential for writing and reading production mainframe code.

IF Statement

Basic IF/ELSE/END-IF

cobol
IF WS-ACCOUNT-STATUS = 'AC'
    PERFORM PROCESS-ACTIVE-ACCOUNT
ELSE
    PERFORM LOG-INACTIVE-ACCOUNT
END-IF.

The END-IF scope terminator explicitly closes the IF block. Always use END-IF in modern COBOL. The older style using a period to close the IF (IF condition ... action.) terminates all open blocks including enclosing IFs and PERFORMs — a common source of subtle bugs.

Compound Conditions

cobol
IF WS-AMOUNT > ZERO
    AND WS-ACCOUNT-STATUS = 'AC'
    AND NOT WS-SUSPENDED
    PERFORM PROCESS-PAYMENT
ELSE
    PERFORM REJECT-PAYMENT
END-IF.

AND, OR, and NOT are the logical operators. Standard precedence: NOT first, then AND, then OR. Use parentheses to override:

cobol
IF (WS-STATUS = 'AC' OR WS-STATUS = 'PD')
    AND WS-AMOUNT > ZERO
    PERFORM ALLOW-TRANSACTION
END-IF.

Abbreviated Conditions

COBOL allows abbreviated compound conditions that re-use the subject:

cobol
*> Full form:
IF WS-CODE = 'A' OR WS-CODE = 'B' OR WS-CODE = 'C'

*> Abbreviated:
IF WS-CODE = 'A' OR 'B' OR 'C'

This only works when the relational operator and subject are the same. It does not work when operators differ.

Nested IF

cobol
IF WS-TRANSACTION-TYPE = 'CREDIT'
    IF WS-AMOUNT > WS-CREDIT-LIMIT
        MOVE 'LIMIT EXCEEDED' TO WS-MESSAGE
        SET WS-ERROR-FOUND TO TRUE
    ELSE
        PERFORM APPLY-CREDIT
    END-IF
ELSE IF WS-TRANSACTION-TYPE = 'DEBIT'
    IF WS-AMOUNT > WS-AVAILABLE-BALANCE
        MOVE 'INSUFFICIENT FUNDS' TO WS-MESSAGE
        SET WS-ERROR-FOUND TO TRUE
    ELSE
        PERFORM APPLY-DEBIT
    END-IF
ELSE
    MOVE 'UNKNOWN TYPE' TO WS-MESSAGE
    SET WS-ERROR-FOUND TO TRUE
END-IF.

Limit nesting to two or three levels. Deeper nesting belongs in separate paragraphs.

88-Level Conditions in IF

88-level condition names produce the cleanest conditional logic:

cobol
*> Instead of:
IF WS-EOF-FLAG = 'Y'

*> Use:
IF WS-EOF

*> Instead of:
IF WS-STATUS-CODE = 'AC' OR WS-STATUS-CODE = 'TR'

*> Use (with appropriate 88-levels defined):
IF WS-ACCOUNT-ACTIVE

EVALUATE Statement

EVALUATE is COBOL's case/switch construct, but significantly more powerful than most languages' switch statements.

EVALUATE with a Single Subject

cobol
EVALUATE WS-TRANSACTION-CODE
    WHEN 'DP'
        PERFORM PROCESS-DEPOSIT
    WHEN 'WD'
        PERFORM PROCESS-WITHDRAWAL
    WHEN 'TF'
        PERFORM PROCESS-TRANSFER
    WHEN 'IQ'
        PERFORM PROCESS-INQUIRY
    WHEN OTHER
        PERFORM HANDLE-UNKNOWN-CODE
END-EVALUATE.

WHEN OTHER catches anything not matched by preceding WHEN clauses. Unlike C/Java switch, there is no fall-through — each WHEN clause executes independently and control exits the EVALUATE after the first match.

EVALUATE TRUE (Condition-Based)

EVALUATE TRUE evaluates each WHEN clause as a Boolean condition:

cobol
EVALUATE TRUE
    WHEN WS-AMOUNT < 0
        PERFORM HANDLE-NEGATIVE-AMOUNT
    WHEN WS-AMOUNT = 0
        PERFORM HANDLE-ZERO-AMOUNT
    WHEN WS-AMOUNT <= 1000
        PERFORM HANDLE-SMALL-AMOUNT
    WHEN WS-AMOUNT <= 10000
        PERFORM HANDLE-MEDIUM-AMOUNT
    WHEN OTHER
        PERFORM HANDLE-LARGE-AMOUNT
END-EVALUATE.

This is the preferred replacement for an IF/ELSE IF chain with unrelated conditions.

EVALUATE with Ranges and ALSO

cobol
EVALUATE WS-SCORE
    WHEN 90 THRU 100
        MOVE 'A' TO WS-GRADE
    WHEN 80 THRU 89
        MOVE 'B' TO WS-GRADE
    WHEN 70 THRU 79
        MOVE 'C' TO WS-GRADE
    WHEN 60 THRU 69
        MOVE 'D' TO WS-GRADE
    WHEN OTHER
        MOVE 'F' TO WS-GRADE
END-EVALUATE.

The ALSO clause evaluates multiple subjects simultaneously — a decision table in one statement:

cobol
EVALUATE WS-ACCOUNT-TYPE ALSO WS-CUSTOMER-CLASS
    WHEN 'SAV' ALSO 'PREM'
        PERFORM APPLY-PREMIUM-SAVINGS-RATE
    WHEN 'SAV' ALSO ANY
        PERFORM APPLY-STANDARD-SAVINGS-RATE
    WHEN 'CHK' ALSO 'PREM'
        PERFORM APPLY-PREMIUM-CHECKING-TERMS
    WHEN 'CHK' ALSO ANY
        PERFORM APPLY-STANDARD-CHECKING-TERMS
    WHEN OTHER ALSO ANY
        PERFORM HANDLE-UNKNOWN-ACCOUNT
END-EVALUATE.

ANY in a WHEN clause is a wildcard — it matches any value for that subject.

PERFORM Statement

PERFORM has four distinct forms: out-of-line paragraph call, inline loop, UNTIL loop, and VARYING loop.

Out-of-Line PERFORM

Calls a named paragraph or section and returns:

cobol
PERFORM INITIALIZE-PROGRAM.
PERFORM READ-NEXT-RECORD.
PERFORM VALIDATE-INPUT-DATA.
PERFORM WRITE-OUTPUT-RECORD.

The called paragraph can itself PERFORM other paragraphs. This is the backbone of structured COBOL design: a main-logic paragraph that reads like a table of contents.

PERFORM THRU

Executes a range of paragraphs from the first through the last:

cobol
PERFORM CALC-TAX THRU CALC-TAX-EXIT.

The EXIT paragraph is typically empty — just a named target:

cobol
CALC-TAX.
    IF WS-TAXABLE
        COMPUTE WS-TAX ROUNDED = WS-AMOUNT * WS-TAX-RATE
    ELSE
        MOVE ZERO TO WS-TAX
    END-IF.

CALC-TAX-EXIT.
    EXIT.

PERFORM THRU is common in older code to use GO TO ... CALC-TAX-EXIT for early exits from complex paragraph sequences. Modern code prefers END-IF/END-PERFORM scope terminators and rarely needs THRU.

PERFORM ... N TIMES

Executes a paragraph or inline block a fixed number of times:

cobol
PERFORM 5 TIMES
    DISPLAY 'RETRY ' WS-RETRY-COUNT
    PERFORM ATTEMPT-CONNECTION
    ADD 1 TO WS-RETRY-COUNT
END-PERFORM.

PERFORM PRINT-PAGE-HEADER 3 TIMES.

PERFORM UNTIL

The fundamental loop construct. Tests the condition before each iteration (TEST BEFORE, the default):

cobol
PERFORM UNTIL WS-EOF
    PERFORM READ-NEXT-RECORD
    IF NOT WS-EOF
        PERFORM PROCESS-RECORD
    END-IF
END-PERFORM.

TEST AFTER makes it a do-while — the body executes at least once:

cobol
PERFORM WITH TEST AFTER UNTIL WS-VALID-INPUT
    PERFORM GET-USER-INPUT
    PERFORM VALIDATE-INPUT
END-PERFORM.

PERFORM VARYING

PERFORM VARYING is the COBOL for-loop with explicit index, start, step, and limit:

cobol
PERFORM VARYING WS-INDEX FROM 1 BY 1
    UNTIL WS-INDEX > WS-ARRAY-SIZE

    MOVE WS-TABLE-ENTRY(WS-INDEX) TO WS-WORK-ITEM
    PERFORM PROCESS-WORK-ITEM

END-PERFORM.

The FROM value is the start, BY is the increment (can be negative for counting down), and UNTIL is the termination condition (tested before each iteration by default).

Nested VARYING iterates two indexes simultaneously — essential for two-dimensional table processing:

cobol
PERFORM VARYING WS-ROW FROM 1 BY 1
    UNTIL WS-ROW > WS-MAX-ROWS
    AFTER WS-COL FROM 1 BY 1
    UNTIL WS-COL > WS-MAX-COLS

    MOVE WS-MATRIX(WS-ROW WS-COL) TO WS-CELL-VALUE
    PERFORM PROCESS-CELL

END-PERFORM.

The AFTER phrase introduces the inner loop variable. The outer loop increments only when the inner loop completes.

Structured Programming Patterns

Main-Logic Pattern

Every production COBOL program should have a single top-level paragraph that reads like a specification:

cobol
PROCEDURE DIVISION.

MAIN-LOGIC.
    PERFORM OPEN-FILES
    PERFORM READ-FIRST-RECORD
    PERFORM PROCESS-ALL-RECORDS UNTIL WS-EOF
    PERFORM WRITE-SUMMARY-TOTALS
    PERFORM CLOSE-FILES
    STOP RUN.

OPEN-FILES.
    OPEN INPUT  CUSTOMER-FILE
    OPEN OUTPUT REPORT-FILE
    IF NOT WS-INPUT-OK
        PERFORM ABEND-OPEN-FAILURE
    END-IF.

READ-FIRST-RECORD.
    READ CUSTOMER-FILE INTO WS-CUSTOMER-REC
        AT END SET WS-EOF TO TRUE
    END-READ.

PROCESS-ALL-RECORDS.
    PERFORM VALIDATE-CUSTOMER-RECORD
    IF WS-RECORD-VALID
        PERFORM GENERATE-REPORT-LINE
        ADD 1 TO WS-RECORDS-PROCESSED
    ELSE
        ADD 1 TO WS-RECORDS-SKIPPED
    END-IF
    PERFORM READ-NEXT-RECORD.

READ-NEXT-RECORD.
    READ CUSTOMER-FILE INTO WS-CUSTOMER-REC
        AT END SET WS-EOF TO TRUE
    END-READ.

Early Exit Pattern

When a paragraph needs to exit early from a loop based on a condition:

cobol
SEARCH-LOOP.
    PERFORM VARYING WS-IDX FROM 1 BY 1
        UNTIL WS-IDX > WS-TABLE-SIZE
           OR WS-FOUND

        IF WS-TABLE-KEY(WS-IDX) = WS-SEARCH-KEY
            SET WS-FOUND TO TRUE
            MOVE WS-IDX TO WS-FOUND-INDEX
        END-IF

    END-PERFORM.

Setting a flag (WS-FOUND) as part of the UNTIL condition is the clean way to early-exit a PERFORM VARYING loop without GO TO.

Error Handling Pattern

cobol
PROCESS-TRANSACTION.
    PERFORM VALIDATE-AMOUNT
    IF WS-ERROR-FOUND
        PERFORM LOG-VALIDATION-ERROR
        GO TO PROCESS-TRANSACTION-EXIT
    END-IF
    PERFORM APPLY-TRANSACTION
    IF WS-ERROR-FOUND
        PERFORM LOG-APPLY-ERROR
        PERFORM ROLLBACK-TRANSACTION
        GO TO PROCESS-TRANSACTION-EXIT
    END-IF
    PERFORM CONFIRM-TRANSACTION.

PROCESS-TRANSACTION-EXIT.
    EXIT.

The GO TO ... EXIT pattern is one of the few accepted uses of GO TO in modern COBOL — it provides a clean single-exit point from a complex paragraph.

Conclusion

Mastering IF, EVALUATE, and PERFORM gives you complete control over COBOL program logic. Use EVALUATE in preference to nested IF chains for three or more conditions, use PERFORM to keep paragraphs short and focused, and always use scope terminators (END-IF, END-EVALUATE, END-PERFORM) to make the block structure explicit and unambiguous.

The next topic is string manipulation — STRING, UNSTRING, INSPECT, and the intrinsic FUNCTION catalogue. See COBOL String Handling, or browse the full COBOL Mastery course.