Capstone: A Complete Batch Payroll System

COBOL COBOL 2014 (ISO/IEC 1989:2014), built with GnuCOBOL 3.x · ✓ verified by execution on 2026-08-16

Welcome to the Expert Capstone. Throughout this course, you have learned the individual pieces of the COBOL puzzle: File I/O, packed decimal arithmetic, and numeric editing. Now, we are going to combine all of those pieces into a single, cohesive application. We will build a complete, runnable Batch Payroll System.

In the real world, mainframe batch jobs run silently in the background, processing millions of records. These programs read an input file, perform arithmetic, detect when groups of records change (a technique known as a control break), and produce a formatted report. Modern languages often abstract this away into objects or database queries, but COBOL relies on explicit, procedural instructions to manage data streams at scale.

Input File COBOL Batch Control Break Logic Report File

The Architecture of a Batch Program

A well-architected batch program is divided into initialization, processing, and termination phases. Let’s look at the foundational SELECT statement, which links our COBOL file definitions to the external files.

cobol ✓ verified output
>>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. EX1.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
    SELECT EMPLOYEE-FILE ASSIGN TO 'EMP.TXT'
    ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD EMPLOYEE-FILE.
01 EMP-REC PIC X(10).
WORKING-STORAGE SECTION.
01 WS-DUMMY PIC X.
PROCEDURE DIVISION.
    DISPLAY 'File definition OK'.
    STOP RUN.
Output
File definition OK

Next, we define our record structures. Notice how we use COMP-3 for arithmetic fields to save space and improve performance, while using numeric-edited PICTURE clauses for the output report.

cobol ✓ verified output
>>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. EX2.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 EMPLOYEE-RECORD.
   05 EMP-ID       PIC 9(5).
   05 EMP-DEPT     PIC X(3).
   05 EMP-HOURS    PIC S9(3)V99 COMP-3 VALUE 40.50.
   05 EMP-RATE     PIC S9(3)V99 COMP-3 VALUE 15.00.
PROCEDURE DIVISION.
    DISPLAY 'Record structure defined'.
    STOP RUN.
Output
Record structure defined

The Processing Loop

The core of any batch program is the READ loop. It is critical to use the FILE STATUS variable to detect when you reach the end of the file. COBOL does not automatically throw exceptions or halt your program if an I/O operation fails. It simply returns a status code. If you ignore it, your program will assume the read was successful and process garbage data.

cobol ✓ verified output
>>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. EX3.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-EOF PIC X VALUE 'N'.
PROCEDURE DIVISION.
    PERFORM UNTIL WS-EOF = 'Y'
       DISPLAY 'Reading...'
       MOVE 'Y' TO WS-EOF
    END-PERFORM.
    DISPLAY 'End of File Reached'.
    STOP RUN.
Output
Reading...
End of File Reached

Inside the loop, we perform our arithmetic calculations. We cannot DISPLAY a COMP-3 variable directly, because it contains packed binary data. We must move it to a numeric-edited field first.

What will happen if we try to display the raw EMP-GROSS value instead of moving it to a numeric-edited variable? Will it print gibberish characters, the correct number without a dollar sign, or crash the program? Let’s find out!

Predict the output cobol

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

>>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. EX4.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 EMP-HOURS    PIC S9(3)V99 COMP-3 VALUE 40.00.
01 EMP-RATE     PIC S9(3)V99 COMP-3 VALUE 25.50.
01 EMP-GROSS    PIC S9(5)V99 COMP-3 VALUE 0.
PROCEDURE DIVISION.
    COMPUTE EMP-GROSS = EMP-HOURS * EMP-RATE.
    DISPLAY "GROSS PAY: " EMP-GROSS.
    STOP RUN.
Output
GROSS PAY: +01020.00

Implementing the Control Break

A control break is a fundamental algorithm in report generation. As we read through a file sorted by department, we compare the current record’s department to the previous record’s department. When they differ, a “break” occurs, and we print the subtotals for the old department before starting the new one. This manual state tracking is why batch COBOL is incredibly fast but requires careful coding.

cobol ✓ verified output
>>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. EX5.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 PREV-DEPT PIC X(3) VALUE '100'.
01 CURR-DEPT PIC X(3) VALUE '101'.
PROCEDURE DIVISION.
    IF CURR-DEPT NOT = PREV-DEPT
       DISPLAY 'Control Break: Dept changed from ' PREV-DEPT ' to ' CURR-DEPT
       MOVE CURR-DEPT TO PREV-DEPT
    END-IF.
    STOP RUN.
Output
Control Break: Dept changed from 100 to 101

Finally, we use numeric editing to format the output for human consumption.

cobol ✓ verified output
>>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. EX6.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 EMP-GROSS       PIC 9(5)V99 VALUE 1020.50.
01 PRT-GROSS-PAY   PIC $$$,$$9.99.
PROCEDURE DIVISION.
    MOVE EMP-GROSS TO PRT-GROSS-PAY.
    DISPLAY 'Pay: ' PRT-GROSS-PAY.
    STOP RUN.
Output
Pay:  $1,020.50

Check yourself

What happens if you try to DISPLAY a COMP-3 (packed decimal) field directly?

Reveal answer

The program prints unreadable binary characters. — COMP-3 stores digits in half-bytes (binary encoded). DISPLAYing it without moving it to a numeric-edited or DISPLAY variable will result in terminal gibberish.

When should you check the FILE STATUS variable?

Reveal answer

After every single I/O operation including OPEN, READ, WRITE, and CLOSE. — Checking FILE STATUS after every single file operation is required to detect full disks, permission issues, or data corruption.

If you do not assign a FILE STATUS variable to a SELECT statement, what happens if an error occurs during a READ?

Reveal answer

The program silently continues processing, potentially reading garbage data. — COBOL does not throw exceptions or crash automatically when a READ fails. Without FILE STATUS, the program assumes success and continues, causing silent corruption.

Challenges

Challenge 1 +50 XP

Fix this gross pay calculation so it adds a $100 bonus to the final amount.

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 " $900.00\n"
Need a hint? (−25% XP)

Add + 100 in the COMPUTE statement.

Show solution (0 XP)
>>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. CH1.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 HOURS PIC 99V99 VALUE 40.00.
01 RATE  PIC 99V99 VALUE 20.00.
01 GROSS PIC 9(4)V99.
01 PRT-GROSS PIC $$$,$$9.99.
PROCEDURE DIVISION.
    COMPUTE GROSS = (HOURS * RATE) + 100.
    MOVE GROSS TO PRT-GROSS.
    DISPLAY PRT-GROSS.
    STOP RUN.

🐞 Bug Hunt +50 XP

Fix this control break logic. It's supposed to print 'BREAK' when the department changes, but it's not working correctly because the previous department isn't being updated.

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 "BREAK\nDONE: 101\n"
Need a hint? (−25% XP)

MOVE the current department to the previous department inside the IF block.

Show solution (0 XP)
>>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. CH2.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 PREV-DEPT PIC X(3) VALUE '100'.
01 CURR-DEPT PIC X(3) VALUE '101'.
PROCEDURE DIVISION.
    IF CURR-DEPT NOT = PREV-DEPT
       DISPLAY 'BREAK'
       MOVE CURR-DEPT TO PREV-DEPT
    END-IF.
    DISPLAY 'DONE: ' PREV-DEPT.
    STOP RUN.