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!
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 outputcobol
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
You predicted
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:
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.
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.
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
Your output
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.