Control Break Logic

COBOL GnuCOBOL 3.2 · ✓ verified by execution on 2026-08-16

When processing large batches of transactions, we often need to calculate totals for groups of related records, such as calculating the total sales for each department in a company.

To accomplish this efficiently as records stream in one by one, COBOL developers use a pattern known as control break logic. Control breaks rely on conditional logic to detect changes in grouped data. As the program reads each record, it compares a specific key (the “control field”) to the previous record’s key. When the key changes, a “break” occurs, signaling the program to print the accumulated totals for the previous group, reset its accumulators, and then start accumulating for the new group.

The Prime Rule: Sort Your Data

Data must be sorted by the control field prior to processing control breaks. The file must provide sorted output to the main routine for grouping. If the data is jumbled, the program will detect a change in the control field every time a different department appears, incorrectly triggering a break on almost every record!

Dept A01 : $150 Dept A01 : $200 Dept A01 : $100 Total A01: $450 Reset accumulator! Dept B02 : $500 Total B02: $500 Reset accumulator! Dept C03 : $300 Dept C03 : $300 Total C03: $600 Reset accumulator! CONTROL BREAK! CONTROL BREAK! EOF BREAK!
A visualization of control break logic. Sorted input records flow in, and changes in the control field trigger a break routine to print and reset the group totals.

The Unsorted Disaster

To truly understand why sorting is mandatory, trace this logic with an unsorted stream (Dept A, then B, then back to A).

Predict the output cobol

Read the code. What exactly will it print? Commit to an answer before you look.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. BAD-SORT.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 P PIC X VALUE 'A'.
       01 C PIC X.
       PROCEDURE DIVISION.
           MOVE 'B' TO C
           PERFORM CHK
           MOVE 'A' TO C
           PERFORM CHK
           STOP RUN.
       CHK.
           IF C NOT = P DISPLAY 'Break ' P.
           MOVE C TO P.
Output
Break A
Break B

This is wrong! Department A broke prematurely when B was read, and B broke when A reappeared. If the data were sorted (A, A, B), we would only get one break per group.

Implementing the Logic

Control break logic typically uses an out-of-line procedure loop to read records. Let’s look at a complete example that processes a sorted dataset of sales records.

Notice three critical mechanics in this code:

  1. The First Record Exception: If you compare the first record against WS-PREV-DEPT (which starts as spaces), a false break triggers instantly. We use a flag (WS-FIRST-RECORD) to simply absorb the very first department key without triggering a break.
  2. Resetting Accumulators: Variables do not reset themselves to zero for the next group. You must explicitly MOVE ZEROS TO WS-DEPT-TOTAL inside the break routine after you have printed the total.
  3. The Final EOF Break: The loop naturally terminates when End-Of-File (EOF) is reached. However, EOF breaks the loop before the final group’s totals are printed! You must explicitly call the total-printing routine one last time after the loop ends.
cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CONTROL-BREAK.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT SALES-FILE ASSIGN TO 'sales.dat'
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  SALES-FILE.
       01  SALES-RECORD.
           05  SR-DEPT          PIC X(3).
           05  SR-AMOUNT        PIC 9(4).
       WORKING-STORAGE SECTION.
       01  WS-EOF-FLAG          PIC X VALUE 'N'.
           88  EOF              VALUE 'Y'.
       01  WS-PREV-DEPT         PIC X(3) VALUE SPACES.
       01  WS-DEPT-TOTAL        PIC 9(5) VALUE ZEROS.
       01  WS-GRAND-TOTAL       PIC 9(6) VALUE ZEROS.
       01  WS-FIRST-RECORD      PIC X VALUE 'Y'.

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           OPEN OUTPUT SALES-FILE
           WRITE SALES-RECORD FROM 'A010150'
           WRITE SALES-RECORD FROM 'A010200'
           WRITE SALES-RECORD FROM 'A010100'
           WRITE SALES-RECORD FROM 'B020500'
           WRITE SALES-RECORD FROM 'C030300'
           WRITE SALES-RECORD FROM 'C030300'
           CLOSE SALES-FILE

           OPEN INPUT SALES-FILE
           PERFORM READ-RECORD
           PERFORM PROCESS-RECORD UNTIL EOF
           
           *> We hit EOF! Don't forget the final group!
           PERFORM PRINT-DEPT-TOTAL
           
           DISPLAY 'GRAND TOTAL: ' WS-GRAND-TOTAL
           CLOSE SALES-FILE
           STOP RUN.

       PROCESS-RECORD.
           *> Handle the very first record silently
           IF WS-FIRST-RECORD = 'Y'
               MOVE SR-DEPT TO WS-PREV-DEPT
               MOVE 'N' TO WS-FIRST-RECORD
           END-IF

           *> Detect the Control Break
           IF SR-DEPT NOT = WS-PREV-DEPT
               PERFORM PRINT-DEPT-TOTAL
               MOVE SR-DEPT TO WS-PREV-DEPT
               MOVE ZEROS TO WS-DEPT-TOTAL
           END-IF

           *> Accumulate totals for the current group
           ADD SR-AMOUNT TO WS-DEPT-TOTAL
           ADD SR-AMOUNT TO WS-GRAND-TOTAL
           DISPLAY '  Sale for dept ' SR-DEPT ': ' SR-AMOUNT
           
           PERFORM READ-RECORD.

       PRINT-DEPT-TOTAL.
           IF WS-PREV-DEPT NOT = SPACES
               DISPLAY '--- TOTAL FOR ' WS-PREV-DEPT ': ' WS-DEPT-TOTAL
           END-IF.

       READ-RECORD.
           READ SALES-FILE
               AT END MOVE 'Y' TO WS-EOF-FLAG
           END-READ.
Output
  Sale for dept A01: 0150
  Sale for dept A01: 0200
  Sale for dept A01: 0100
--- TOTAL FOR A01: 00450
  Sale for dept B02: 0500
--- TOTAL FOR B02: 00500
  Sale for dept C03: 0300
  Sale for dept C03: 0300
--- TOTAL FOR C03: 00600
GRAND TOTAL: 001550

By mastering this pattern, you can now write standard batch reports summarizing millions of sorted transaction records!

Check yourself

What is the most important prerequisite before applying control break logic to a file?

Reveal answer

The file must be sorted by the control field — Control breaks only detect changes in the stream as they are read. If the file is not pre-sorted by the control key, identical keys spread across the file will trigger false breaks.

Why do you need to save the control field of the first record explicitly?

Reveal answer

To prevent an immediate false control break on the very first read — The first record read sets the initial value of the control key. If you compare it against the initial spaces/zeros of your working-storage variable, a false break triggers immediately.

What must you remember to do after the main processing loop finishes?

Reveal answer

Explicitly trigger the break routine one last time for the final group — When the AT END condition (EOF) breaks the reading loop, the totals for the final group have been accumulated but not yet printed. You must explicitly trigger the break logic for this final group.

Challenges

Challenge 1 +15 XP

Complete the `IF` statement to detect a control break. The current record's department is `SR-DEPT` and the previous one is saved in `WS-PREV-DEPT`.

cobol ✓ solution verified at build time

COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the fixed code should be, then open the solution and compare. The expected output below is real — it came from compiling the solution with GnuCOBOL when this page was built.

  • Test 1 — expects "Break detected"
Need a hint? (−25% XP)

Compare the current department against the saved previous department using NOT =.

Show solution (0 XP)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CHALLENGE.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-PREV-DEPT PIC X(3) VALUE 'A01'.
       01 SR-DEPT PIC X(3) VALUE 'B02'.
       PROCEDURE DIVISION.
       IF SR-DEPT NOT = WS-PREV-DEPT
           DISPLAY 'Break detected'
       END-IF.
       STOP RUN.

Challenge 2 +15 XP

After printing the department total, reset the accumulator `WS-DEPT-TOTAL` to prepare for the next group.

cobol ✓ solution verified at build time

COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the fixed code should be, then open the solution and compare. The expected output below is real — it came from compiling the solution with GnuCOBOL when this page was built.

  • Test 1 — expects "Total: 00500"
Need a hint? (−25% XP)

Use the MOVE verb to set WS-DEPT-TOTAL to ZEROS.

Show solution (0 XP)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CHALLENGE.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-DEPT-TOTAL PIC 9(5) VALUE 00500.
       PROCEDURE DIVISION.
       PRINT-DEPT-TOTAL.
           DISPLAY 'Total: ' WS-DEPT-TOTAL
           MOVE ZEROS TO WS-DEPT-TOTAL.
       STOP RUN.

🐞 Bug Hunt +20 XP

This developer believes that a control break loop naturally prints the last group's totals when it hits EOF. Run the program—notice that the final total is missing! Fix the main routine so that the final group's total is printed before the program ends.

This code runs. It just does the wrong thing. Read it, find the defect, fix it — the tests below decide when you are right.

cobol ✓ solution verified at build time

COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the fixed code should be, then open the solution and compare. The expected output below is real — it came from compiling the solution with GnuCOBOL when this page was built.

  • Test 1 — expects "Reading records...\nFINAL TOTAL: 0500\n"
Need a hint? (−25% XP)

After the MAIN-LOOP finishes, you must explicitly PERFORM PRINT-TOTAL one last time.

Show solution (0 XP)
IDENTIFICATION DIVISION.
PROGRAM-ID. BUGHUNT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-EOF PIC X VALUE 'N'.
01 WS-TOTAL PIC 9(4) VALUE 0500.
PROCEDURE DIVISION.
    PERFORM MAIN-LOOP UNTIL WS-EOF = 'Y'.
    PERFORM PRINT-TOTAL.
    STOP RUN.
MAIN-LOOP.
    DISPLAY 'Reading records...'
    MOVE 'Y' TO WS-EOF.
PRINT-TOTAL.
    DISPLAY 'FINAL TOTAL: ' WS-TOTAL.