◄ HOME ⚙ COBOL SYNTAX EXPLORER ► ARCADE
⏰ Countdown to Billing System Walkthrough
Calculating...

⚙ COBOL SYNTAX EXPLORER

Rib IT Ltd — Internal Reference Tool v1.1
SYSTEM ONLINE 📚 28 Topics 🐸 Jimothy Frogbit 📅 28 Jun 2026
IDENTIFICATION ENVIRONMENT DATA PROCEDURE FILE I/O STRINGS ✦ CONDITIONALS BILLING ✦
IDENTIFICATION DIVISION. The ID card for every COBOL program

PROGRAM-ID. Required

IDENTIFICATION DIVISION.
PROGRAM-ID. ProgramName.

The only REQUIRED paragraph in the IDENTIFICATION DIVISION. Every COBOL program must have a PROGRAM-ID. It's the name the operating system uses to call the program.

Optional Paragraphs

These are all optional but good practice for documentation:

IDENTIFICATION DIVISION. PROGRAM-ID. BillingModule. AUTHOR. Jimothy Frogbit. DATE-WRITTEN. 2026-06-28. DATE-COMPILED. (compiler fills this in) SECURITY. Rib IT Internal Use Only.

PITFALL: COBOL columns still matter on mainframes. Columns 1-6 = sequence numbers, col 7 = indicator area (* comment, - continuation, D debug), cols 8-11 = Area A (DIVISION, SECTION, paragraph names), cols 12-72 = Area B (statements). Free format is allowed on most modern compilers.

ENVIRONMENT DIVISION. Where your program meets the machine

CONFIGURATION SECTION

SOURCE-COMPUTER. IBM-370.
OBJECT-COMPUTER. IBM-370.

Documents what computer compiled it and what computer runs it. On modern systems (GnuCOBOL, Micro Focus) these are mostly documentation — but on mainframes, the OBJECT-COMPUTER can affect code generation.

INPUT-OUTPUT SECTION Critical for files

FILE-CONTROL.
SELECT input-file ASSIGN TO "input.dat"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT output-file ASSIGN TO "output.dat"
ORGANIZATION IS LINE SEQUENTIAL.

The FILE-CONTROL paragraph maps internal file names (used in the DATA DIVISION) to external filenames or DD names. This is where you define file organisation — crucial for getting I/O right.

OrganizationUse Case
LINE SEQUENTIALText files, one record per line
SEQUENTIALFixed-length records, no delimiters
INDEXEDKeyed access — like a simple database
RELATIVERecord number access — position-based
DATA DIVISION. Where all the variables live

WORKING-STORAGE SECTION Most common

Global variables for your program. Defined with level numbers and PICTURE clauses.

WORKING-STORAGE SECTION. 77 WS-TOTAL PIC 9(6)V99 VALUE ZEROS. 01 WS-CUSTOMER-RECORD. 05 WS-CUST-ID PIC 9(5). 05 WS-CUST-NAME PIC X(30). 05 WS-CUST-BALANCE PIC S9(7)V99. 05 WS-CUST-STATUS PIC X. 88 WS-ACTIVE VALUE "A". 88 WS-INACTIVE VALUE "I". 05 WS-CUST-ZIP PIC 9(5).

PICTURE Clause Core concept

The PICTURE (PIC) clause defines the data type and size. Every elementary item needs one.

SymbolMeaningExample
9Numeric digit (0-9)PIC 9(4) = 4 digits
XAlphanumeric characterPIC X(30) = 30 chars
AAlphabetic (A-Z, space)PIC A(20)
SSigned numericPIC S9(5) = ±99999
VImplied decimal pointPIC 9(3)V99 = 999.99
PScaling positionPIC 9(4)PP = ×100

PITFALL: PIC 9(3)V99 stores 5 bytes (3+2) but the V is implied — there's no actual decimal point in storage. COBOL aligns the decimal in arithmetic. Getting V wrong is how you bill someone £999.99 when you meant £9.99.

Level Numbers Hierarchy matters

Level numbers create the record hierarchy. Group items (01, 05, 10) contain subordinate items.

LevelUse
01Record description — top level
02-49Group or elementary items
66RENAMES clause
77Standalone elementary item (no group)
88Condition name — not a variable, a condition
78CONSTANT (some compilers)

PITFALL: Level 88 is NOT a variable — you can't MOVE to it. It's a condition that's TRUE when its parent equals the specified value. Beginners always try to use 88s as variables. Don't be that frog.

REDEFINES Advanced

05 WS-RAW-FIELD PIC X(10).
05 WS-RAW-FIELD-R REDEFINES WS-RAW-FIELD.
10 WS-RAW-PART1 PIC 9(5).
10 WS-RAW-PART2 PIC 9(5).

REDEFINES lets you view the same bytes with a different structure. Critical for parsing legacy file formats. The redefining item must have the same (or larger) size. Both names share the same storage — changing one changes the other.

VALUE Clause

Initial values for variables. Without VALUE, numeric fields start as undefined (garbage!).

77 WS-COUNT PIC 9(4) VALUE ZEROS. 77 WS-NAME PIC X(20) VALUE SPACES. 77 WS-FLAG PIC X VALUE "N". 01 WS-HEADER VALUE "BILLING ". 05 WS-HDR-TYPE PIC 9(4). 05 WS-HDR-DATE PIC 9(8).

Common figurative constants: ZEROS, SPACES, HIGH-VALUES, LOW-VALUES, QUOTES, ALL "x"

PROCEDURE DIVISION. The actual code — what the program DOES

MOVE Most common verb

MOVE source TO destination.

COBOL's assignment operator. Copies data from source to destination, respecting PICTURE clauses. Automatic type conversion (sort of) — alphanumeric to numeric, truncation or padding as needed.

MOVE 100 TO WS-TOTAL. MOVE "JIMOTHY" TO WS-NAME. MOVE ZEROS TO WS-COUNT, WS-SUM.

PITFALL: MOVE SPACES TO a PIC 9 field gives you a runtime error. MOVE "ABC" TO PIC 9 gives you... undefined behaviour. Know your types!

ADD / SUBTRACT / MULTIPLY / DIVIDE

COBOL's arithmetic verbs. More readable than + - * / symbols.

ADD 1 TO WS-COUNT. ADD WS-PRICE TO WS-TOTAL. SUBTRACT WS-DISCOUNT FROM WS-TOTAL. MULTIPLY WS-RATE BY WS-AMOUNT GIVING WS-RESULT. DIVIDE WS-TOTAL BY WS-COUNT GIVING WS-AVG REMAINDER WS-REM.

ROUNDED option: ADD 1.005 TO WS-TOTAL ROUNDED — but check the compiler's rounding mode. GnuCOBOL rounds away from zero; some mainframes round to even.

PERFORM The most beautiful verb

COBOL's control flow — called, executed, and returned from. My dissertation topic! Multiple forms:

* Inline PERFORM (modern COBOL): PERFORM 100 TIMES ADD 1 TO WS-COUNT END-PERFORM. * Named paragraph: PERFORM 100-CALCULATE-TOTAL. * PERFORM UNTIL (my favourite): PERFORM UNTIL WS-EOF OR WS-COUNT > 1000 READ input-file INTO WS-RECORD AT END MOVE "Y" TO WS-EOF END-READ ADD 1 TO WS-COUNT END-PERFORM. * PERFORM VARYING (FOR loop): PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 100 DISPLAY "Item: " WS-I END-PERFORM.

PITFALL: In a PERFORM THRU (or THROUGH), don't let execution fall through into the next paragraph. Every paragraph should end with EXIT, EXIT PROGRAM, or GO TO. Falling through is the COBOL equivalent of a missing return statement.

IF / ELSE / END-IF

IF WS-BALANCE > 0 DISPLAY "Customer owes: " WS-BALANCE IF WS-BALANCE > 1000 MOVE "O" TO WS-OVERDUE-FLAG END-IF ELSE DISPLAY "Balance is zero or negative" END-IF.

END-IF terminates the IF block. Without it, the IF spills into the next statement! Always pair IF with END-IF. Nest freely but format carefully.

EVALUATE (switch/case) Modern COBOL

COBOL's version of switch/case — much cleaner than nested IFs.

EVALUATE WS-TRANSACTION-CODE WHEN "ADD" PERFORM 200-ADD-CUSTOMER WHEN "UPD" PERFORM 300-UPDATE-CUSTOMER WHEN "DEL" PERFORM 400-DELETE-CUSTOMER WHEN WHEN OTHER DISPLAY "Unknown code: " WS-TRANSACTION-CODE END-EVALUATE.

EVALUATE also supports TRUE/FALSE conditions, ranges, and multiple conditions. Very powerful. My Bog Valley lecturer called it "COBOL's hidden gem."

GO TO Use sparingly

GO TO 999-EXIT-PROGRAM.

GO TO branches unconditionally. It works, but structured programming (PERFORM) is almost always better. In the billing system, expect to see GO TO used for error handling and early exits — that's the legacy pattern. Don't write new code with it unless Froggy's system expects it.

PITFALL: GO TO out of a PERFORM paragraph breaks the call stack. ALWAYS use EXIT PARAGRAPH or EXIT SECTION instead of GO TO to return from a performed paragraph.

88-Level Conditions

Level 88 condition names make IF statements read like English.

* In WORKING-STORAGE: 05 WS-CUST-STATUS PIC X. 88 WS-ACTIVE VALUE "A". 88 WS-INACTIVE VALUE "I". 88 WS-SUSPENDED VALUE "S". * Usage: IF WS-ACTIVE PERFORM 500-PROCESS-ACTIVE ELSE IF WS-SUSPENDED DISPLAY "Account suspended" END-IF END-IF.

CALL / EXIT PROGRAM

CALL "SubProgramName" USING WS-PARAM1, WS-PARAM2.

Calling subprograms. Parameters must match the called program's LINKAGE SECTION. This is how COBOL modules talk to each other — expect to see CALL in the billing system's main dispatch module.

CALL "Billing-Calc" USING WS-CUSTOMER-ID WS-AMOUNT-DUE WS-TAX-RATE WS-RESULT.

EXIT PROGRAM returns control to the caller. STOP RUN terminates the entire run unit.

FILE I/O Reading and writing files — the beating heart of batch COBOL

OPEN / CLOSE

OPEN INPUT customer-file. OPEN OUTPUT report-file. OPEN I-O transaction-file. * Read AND write OPEN EXTEND log-file. * Append CLOSE customer-file. CLOSE report-file, transaction-file.

READ / WRITE

READ customer-file INTO WS-CUSTOMER-REC AT END MOVE "Y" TO WS-EOF END-READ. WRITE WS-REPORT-LINE FROM WS-HEADER AFTER ADVANCING 1 LINE. WRITE WS-RECORD FROM WS-CUSTOMER-REC.

PITFALL: READ AT END fires when there are no more records. Always handle this — without it, a READ past EOF causes a runtime error. The FILE STATUS variable helps: 00 = success, 10 = end of file, 30 = permanent error.

FILE STATUS Checking

* In FILE-CONTROL: SELECT customer-file ASSIGN TO "CUSTOMER.DAT" ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-FILE-STATUS. * In WORKING-STORAGE: 77 WS-FILE-STATUS PIC XX. * After every I/O operation: IF WS-FILE-STATUS NOT = "00" DISPLAY "File error: " WS-FILE-STATUS PERFORM 999-HANDLE-ERROR END-IF.

FILE STATUS is the most important thing you can check. Without it, a failed READ silently produces garbage data. Froggy once told me: "Every file operation without a FILE STATUS check is a bug waiting to become a support ticket." He was right.

FILE STATUS 92 — Dataset Not Released

* Most common billing system error: IF WS-FILE-STATUS = "92" DISPLAY "Dataset not released from yesterday's run" DISPLAY "Release dataset and resubmit job" MOVE 16 TO RETURN-CODE STOP RUN END-IF.

FILE STATUS 92 means the dataset is still allocated from a previous job run that didn't properly CLOSE or release it. This is NOT a program bug — it's an operational issue. The fix: release the dataset (via the operator console or JCL) and resubmit. Froggy told me: "The most common 'program error' I see is actually FILE STATUS 92. The program is fine. The tape wasn't released." Common causes: job ABEND'd without cleanup, operator forgot to dismount overnight tape, or two jobs share the same dataset name.

🐸 Froggy's Rule: "FILE STATUS 92 is not a code problem. It is a tape problem. Check the operator console before you check the program. 90% of my early-morning calls were FILE STATUS 92. 10% were actual bugs. Guess which one I checked first."

REEL/UNIT Status — Tape Mounting

* REEL status check before first READ: SELECT BILLING-TAPE ASSIGN TO "BILLING.MASTER.TAPE" ORGANIZATION IS SEQUENTIAL FILE STATUS IS WS-TAPE-STATUS. * Check REEL is mounted: OPEN INPUT BILLING-TAPE. IF WS-TAPE-STATUS NOT = "00" DISPLAY "REEL BILLING.MASTER NOT MOUNTED" WRITE OPERATOR-MESSAGE FROM WS-WAIT-MSG * System WAITs — does NOT crash WAIT END-IF.

REEL status is a tape-specific check that verifies the physical tape is mounted before any I/O begins. Unlike a DISK file, a tape drive requires human intervention to mount the reel. A well-designed system does NOT crash if the tape is missing — it waits for a human operator to mount it, then proceeds. The WAIT statement is COBOL's way of saying "pause until a human fixes this." The INIT phase of a billing pipeline always includes: (1) allocate files, (2) check REEL status, (3) validate date parameters. If any of those fail, do not proceed.

🐸 Froggy's Rule: "A system that waits for a tape is better than a system that crashes without one. The WAIT gives the operator time to mount the reel. A crash gives everyone a phone call at 02:00. Choose the WAIT."
STRINGS ✦ STRING, UNSTRING, INSPECT — COBOL's surprising text power

STRING (concatenation)

STRING WS-FIRST-NAME DELIMITED BY SPACES ", " DELIMITED BY SIZE WS-LAST-NAME DELIMITED BY SPACES INTO WS-FULL-NAME ON OVERFLOW DISPLAY "Name too long!" END-STRING.

STRING concatenates multiple pieces into one field. Each piece is delimited (usually by SPACES or SIZE). ON OVERFLOW fires if the target is too small.

UNSTRING (split)

UNSTRING WS-FULL-NAME DELIMITED BY "," OR SPACES INTO WS-LAST-NAME WS-FIRST-NAME END-UNSTRING.

UNSTRING splits a field into multiple targets based on delimiters. The delimiter itself is consumed. Multiple delimiters can be specified with OR. COUNTING and POINTER options give you fine control.

INSPECT (count/replace)

* Count occurrences: INSPECT WS-STRING TALLYING WS-COUNT FOR ALL ",". * Replace all commas with semicolons: INSPECT WS-STRING REPLACING ALL "," BY ";". * Replace leading zeros: INSPECT WS-STRING REPLACING LEADING ZEROS BY SPACES.

INSPECT is COBOL's text processing swiss army knife. TALLYING counts, REPLACING substitutes. CHARACTERS, ALL, LEADING, FIRST give you precision.

CONDITIONALS Class conditions, sign tests, and logical operators

Class Conditions

Test what kind of data a field contains — before using it.

IF WS-AMOUNT IS NUMERIC ADD WS-AMOUNT TO WS-TOTAL ELSE DISPLAY "Non-numeric data in amount field!" END-IF. IF WS-NAME IS ALPHABETIC DISPLAY "Valid name" END-IF.

IS NUMERIC, IS ALPHABETIC, IS ALPHABETIC-UPPER — vital for validating input from files. IS NUMERIC returns false if the field has SPACES or non-numeric characters.

Sign Tests

IF WS-BALANCE IS POSITIVE PERFORM 500-SEND-BILL ELSE IF WS-BALANCE IS NEGATIVE PERFORM 600-PROCESS-CREDIT ELSE PERFORM 700-PROCESS-ZERO END-IF END-IF.

IS POSITIVE (> 0), IS NEGATIVE (< 0), IS ZERO (= 0). Works with signed numeric fields (PIC S9...).

Compound Conditions

IF WS-AMOUNT > 100 AND WS-AMOUNT < 500 PERFORM 400-APPROVE-MANUAL END-IF. IF WS-ACTIVE OR WS-VIP PERFORM 800-PROCESS-NOW END-IF. IF NOT WS-INACTIVE PERFORM 900-SEND-REMINDER END-IF.

AND, OR, NOT work as expected. Parentheses for grouping: IF (A AND B) OR (C AND D).

BILLING & FINANCIAL COBOL Money, rounding, and batch job patterns — for Monday's walkthrough

Financial PICTURE Clauses Critical for billing

Money in COBOL uses signed numeric fields with an implied decimal point. Get this wrong and you bill someone £999.99 for a £9.99 item — or worse, the reverse.

01 WS-AMOUNT-DUE PIC S9(7)V99 VALUE ZEROS. 01 WS-TAX-RATE PIC S99V9(4) VALUE ZEROS. * 0.2000 = 20% 01 WS-TOTAL PIC S9(9)V99 VALUE ZEROS.
PatternSizeMax ValueUse Case
PIC S9(5)V998 digits£99,999.99Line items, discounts
PIC S9(7)V9910 digits£9,999,999.99Invoices, customer balances
PIC S9(9)V9912 digits£999,999,999.99Batch totals, running sums
PIC S9(11)V9914 digits£99,999,999,999.99Ledger-level grand totals

⚠ THE IMPLIED DECIMAL PITFALL: PIC 9(3)V99 stores 5 bytes but the decimal is implied — there's no dot in storage. If you display a PIC 9(3)V99 with value 12345, you see "12345" not "123.45". COBOL tracks the decimal position in arithmetic, but file output and DISPLAY show the raw digits. In the billing system, look for how they handle decimal display — there's usually a separate display field with an explicit decimal.

Froggy's Rule: "Always check your V positions before and after arithmetic. One off-by-one decimal error and you've billed someone a thousand times what they owe. I've seen it happen. Twice."

ROUNDED Arithmetic Compiler-dependent

COBOL's ROUNDED option controls what happens when the result has more decimal places than the target field.

COMPUTE WS-TOTAL ROUNDED = WS-SUBTOTAL + WS-TAX. * Without ROUNDED, 10.5 / 3 gives 3.5, stored in PIC 9(3) as 3 (truncated!). * With ROUNDED, 10.5 / 3 gives 3.5, stored in PIC 9(3) as 4 (rounded up).

CRITICAL: Different COBOL compilers round differently! GnuCOBOL rounds away from zero (0.005 → 0.01). Some mainframe compilers round to even (banker's rounding: 0.005 → 0.00, 0.015 → 0.02). In a billing system, this difference can cost (or save) real money over millions of transactions.

Ask Froggy on Monday: "What rounding mode does the billing compiler use? Is there a rounding standard documented somewhere?"

Batch Job Pattern: READ-PROCESS-WRITE The billing backbone

The standard batch billing pattern. Every billing run follows this structure:

PROCEDURE DIVISION. OPEN INPUT transaction-file. OPEN OUTPUT billing-file. OPEN OUTPUT error-report. MOVE "N" TO WS-EOF. PERFORM UNTIL WS-EOF = "Y" READ transaction-file INTO WS-TRANSACTION AT END MOVE "Y" TO WS-EOF NOT AT END PERFORM 100-PROCESS-TRANSACTION END-READ END-PERFORM. CLOSE transaction-file. CLOSE billing-file. CLOSE error-report. STOP RUN. 100-PROCESS-TRANSACTION. IF WS-TRANS-AMOUNT IS NUMERIC COMPUTE WS-BILL-AMOUNT ROUNDED = WS-TRANS-AMOUNT * WS-RATE IF WS-TRANS-TYPE = "INV" WRITE WS-BILL-REC FROM WS-INVOICE ELSE WRITE WS-ERROR-LINE FROM WS-ERROR-MSG END-IF ELSE WRITE WS-ERROR-LINE FROM WS-BAD-AMOUNT END-IF.

Froggy's Rule: "Every billing run needs THREE outputs: the billing file (success), the error report (failures), and a control total (did the numbers balance?). If you only have two, you're missing reconciliation data."

Error Handling in Billing Ask Froggy about this

Billing systems MUST handle errors gracefully — one bad record shouldn't kill the whole batch.

* FILE STATUS check after every I/O: READ transaction-file INTO WS-RECORD INVALID KEY DISPLAY "Read error on transaction: " WS-FILE-STATUS PERFORM 900-LOG-ERROR ADD 1 TO WS-ERROR-COUNT CONTINUE * Don't ABEND — process the next record! END-READ.

Key principles for billing error handling:

  • Never STOP RUN on a bad record — log it and continue. One bad customer shouldn't block 50,000 others.
  • Track error counts — if errors exceed a threshold, the operator needs to know.
  • Return codes matter — the JCL (job control language) checks the return code. 0 = success, 4 = warnings, 8 = errors, 12 = severe errors, 16 = ABEND.
  • Control totals — count records in vs records out. If they don't match, the batch needs investigation.

Ask Froggy on Monday: "How does the billing system handle partial failures — does it write an audit trail for every skipped record? What's the threshold before it ABENDs the whole batch?"