COBOL GnuCOBOL 3.2 ·
✓ verified by execution on 2026-08-16
Welcome to the Sales Report capstone project! In this lesson, we will integrate everything you have learned about COBOL—from basic DATA DIVISION structures and PICTURE clauses, to PERFORM loops and sequential file handling—into a single, robust batch program.
Historically, COBOL was designed exactly for this kind of work: reading massive sequential files, performing control break logic on groups of data, and formatting the output into a clean, human-readable printed report. Records in sequential files are processed in a strict sequence, one after another. Because of this, it is crucial that your data is pre-sorted before it hits your report program.
Let’s visualize how our report program processes data in batches:
The Capstone Project
Here is the complete program. Notice how the AT END imperative statement is used on the READ command to detect when the file is empty, triggering the final control break output.
cobol✓ verified output
IDENTIFICATION DIVISION. PROGRAM-ID. SALES-REPORT. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT INPUT-FILE ASSIGN TO 'input.dat' ORGANIZATION IS LINE SEQUENTIAL. DATA DIVISION. FILE SECTION. FD INPUT-FILE. 01 IN-RECORD. 05 IN-REGION PIC X(5). 05 IN-AMOUNT PIC 9(4). WORKING-STORAGE SECTION. 01 WS-EOF-FLAG PIC X VALUE 'N'. 88 EOF VALUE 'Y'. 01 WS-PREV-REGION PIC X(5) VALUE SPACES. 01 WS-REGION-TOTAL PIC 9(6) VALUE ZEROS. 01 WS-FIRST-RECORD PIC X VALUE 'Y'. PROCEDURE DIVISION. MAIN-LOGIC. OPEN OUTPUT INPUT-FILE WRITE IN-RECORD FROM 'NORTH0150' WRITE IN-RECORD FROM 'NORTH0200' WRITE IN-RECORD FROM 'SOUTH0500' CLOSE INPUT-FILE OPEN INPUT INPUT-FILE PERFORM READ-RECORD PERFORM PROCESS-RECORD UNTIL EOF PERFORM PRINT-TOTAL CLOSE INPUT-FILE STOP RUN. PROCESS-RECORD. IF WS-FIRST-RECORD = 'Y' MOVE IN-REGION TO WS-PREV-REGION MOVE 'N' TO WS-FIRST-RECORD END-IF IF IN-REGION NOT = WS-PREV-REGION PERFORM PRINT-TOTAL MOVE IN-REGION TO WS-PREV-REGION MOVE ZEROS TO WS-REGION-TOTAL END-IF ADD IN-AMOUNT TO WS-REGION-TOTAL PERFORM READ-RECORD. PRINT-TOTAL. IF WS-PREV-REGION NOT = SPACES DISPLAY 'Total for ' WS-PREV-REGION ': ' WS-REGION-TOTAL END-IF. READ-RECORD. READ INPUT-FILE AT END MOVE 'Y' TO WS-EOF-FLAG END-READ.
Output
Total for NORTH: 000350
Total for SOUTH: 000500
Your output
Report Formatting
A report isn’t useful if it’s hard to read. You can add headers to your reports to make the context immediately obvious:
Additionally, numeric variables with leading zeros are visually cluttered. You can format report output cleanly using zero suppression (the Z character) in your PICTURE clauses:
cobol✓ verified output
IDENTIFICATION DIVISION. PROGRAM-ID. ZSUP. DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-NUM PIC 9(4) VALUE 0050. 01 WS-FMT PIC ZZZ9. PROCEDURE DIVISION. MOVE WS-NUM TO WS-FMT. DISPLAY WS-FMT. STOP RUN.
Output
50
Your output
Data Truncation and Accumulators
A very common logic bug in COBOL batch programs occurs when an accumulator is defined with a PICTURE clause that is too small for the sum of its inputs. COBOL will silently truncate the high-order digits of the result.
Predict the outputcobol
Read the code. What exactly will it print? Commit to an answer before you look.
This is incredibly dangerous in financial reports because no error is thrown! The WRITE statement writes a new record to an open file, but if your accumulator silently overflowed, your final printed report will contain incorrect math. Variables such as accumulators can be reset to their default values using the INITIALIZE statement, or by moving ZEROS into them (as we did in our capstone project), but if they are too small, resetting them won’t prevent truncation on large batches.
Dealing with File Status
When dealing with batch input and output, missing files are a reality. If a previous job failed to run, your input file might simply not exist. Handling file open errors gracefully in a batch program requires checking the FILE STATUS variable.
cobol✓ verified output
IDENTIFICATION DIVISION. PROGRAM-ID. FSTAT. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT IN-FILE ASSIGN TO 'missing.dat' ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-STAT. DATA DIVISION. FILE SECTION. FD IN-FILE. 01 IN-REC PIC X. WORKING-STORAGE SECTION. 01 WS-STAT PIC XX. PROCEDURE DIVISION. OPEN INPUT IN-FILE. DISPLAY 'STATUS: ' WS-STAT. STOP RUN.
Output
STATUS: 35
Your output
If the status is not 00, your program should gracefully abort rather than continuing to process garbage data. In production environments, continuing with a missing input file can be disastrous. Let’s fix a silent truncation bug in the challenge below to solidify your understanding of accumulators.
Check yourself
Why is it important to have accumulators sized larger than their input fields?
Reveal answer
To prevent silent overflow/truncation. — Accumulators must be sized larger than their input fields to prevent silent overflow/truncation when adding many records together.
What happens when a program hits EOF while reading a sequential file?
Reveal answer
The AT END condition is triggered, stopping the read loop. — EOF only stops the read loop; you must manually trigger the final control break logic using the AT END phrase.
Why check the FILE STATUS variable when opening a batch input file?
Reveal answer
To prevent processing garbage data if the file is missing or corrupt. — If the file doesn't exist (e.g. status 35), you must handle it gracefully instead of continuing to read from a missing source.
Challenges
🐞 Bug Hunt+25 XP
The total accumulator is too small and is truncating the sum of sales. Fix its PICTURE clause so it can hold the value 1500.
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 "Total: 1500"
Need a hint? (−25% XP)
Increase the size of WS-TOTAL from PIC 9(3) to PIC 9(4).
Show solution (0 XP)
IDENTIFICATION DIVISION. PROGRAM-ID. CHALLENGE. DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-SALES-1 PIC 9(3) VALUE 800. 01 WS-SALES-2 PIC 9(3) VALUE 700. 01 WS-TOTAL PIC 9(4) VALUE 0. PROCEDURE DIVISION. ADD WS-SALES-1 TO WS-TOTAL ADD WS-SALES-2 TO WS-TOTAL DISPLAY 'Total: ' WS-TOTAL STOP RUN.
Challenge 2 +25 XP
Use an edited PICTURE clause (ZZZ9) to remove the leading zeros from the final output.
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 "Amt: 50"
Need a hint? (−25% XP)
Change the PICTURE clause of WS-FORMATTED to use Zs.
Show solution (0 XP)
IDENTIFICATION DIVISION. PROGRAM-ID. CHALLENGE. DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-TOTAL PIC 9(4) VALUE 0050. 01 WS-FORMATTED PIC ZZZ9. PROCEDURE DIVISION. MOVE WS-TOTAL TO WS-FORMATTED DISPLAY 'Amt: ' WS-FORMATTED STOP RUN.