CUCO common mistakes
CUCO · Understand the traps and how to avoid them.
Writing numeric output with a PICTURE clause that lacks an explicit sign or decimal position (for example PIC 9(5) for a value that can be negative or fractional), then wondering why the stored value is truncated or why a negative result displays as a positive number.
Candidates memorize PICTURE syntax as decoration rather than as a storage contract. They forget that 9(5) allocates five unsigned digit positions with no decimal point, so the sign is dropped and any fractional part is truncated according to the receiving field, not the source value.
What to do instead
Choose the PICTURE from the data's range and scale before writing code: add S for any field that can go negative, insert V where the implied decimal point belongs, and use 9(n)V99 or S9(n)V99 deliberately. When moving between fields, check that the receiving field can hold sign and scale, and remember that a move to a field without V truncates rather than rounds.
Assuming WORKING-STORAGE items are reinitialized on every call or every program invocation, and relying on that assumption to reset counters or flags between runs.
Candidates conflate WORKING-STORAGE with LOCAL-STORAGE. WORKING-STORAGE is allocated and initialized once when the program is first loaded; a subprogram called repeatedly keeps its previous values unless the program is CANCELed or the items are explicitly reinitialized. A stale counter then produces wrong totals on the second call.
What to do instead
Use LOCAL-STORAGE SECTION for items that must start fresh on each invocation, or explicitly initialize WORKING-STORAGE items with VALUE clauses plus a reinitialization paragraph at the start of the procedure. When testing a called subprogram, verify behavior on the second and third call, not just the first.
Building a PERFORM loop that never terminates because the loop-control variable is modified inside the performed paragraph or because the loop uses PERFORM ... UNTIL with a condition that can never become true.
Candidates treat PERFORM as a for-loop and assume the control variable is protected. In COBOL, an inline or out-of-line PERFORM with TEST BEFORE or TEST AFTER evaluates the condition at a defined point, and any statement inside the performed range that changes the control item can skip the terminating value or cause an infinite loop. Off-by-one errors also arise from confusing TEST BEFORE with TEST AFTER.
What to do instead
Keep the loop-control variable out of the performed paragraph's body, or use an index or counter that only the loop modifies. Decide explicitly between TEST BEFORE (zero-trip possible) and TEST AFTER (at least one trip), and trace the first and last iterations by hand. Add a defensive maximum-iteration counter when the termination condition depends on input data.
Mixing GO TO with structured constructs so that control falls into a paragraph that was meant to run only via PERFORM, causing a paragraph to execute twice or a range to be entered mid-way.
COBOL paragraphs are not scoped blocks; execution falls through from one paragraph to the next unless a STOP RUN, GOBACK, or EXIT PROGRAM intervenes. Candidates coming from block-structured languages assume a paragraph ends at its next header. A GO TO into the middle of a PERFORM range also corrupts the return mechanism.
What to do instead
End every performed paragraph with an explicit exit (EXIT PARAGRAPH or a GOBACK/STOP RUN at the top level) or order paragraphs so fall-through is intentional. Never GO TO into a PERFORM range. Prefer inline PERFORM and IF/EVALUATE over GO TO, and review the procedure division top-to-bottom checking that every paragraph has exactly one entry path.
Reading a sequential file without checking the FILE STATUS after each I/O statement, so an AT END condition is handled but a duplicate-key, record-locked, or file-not-found status is silently ignored.
Candidates learn the AT END phrase and treat it as complete error handling. AT END covers only the end-of-file condition; other statuses (for example 22 duplicate key, 23 not found, 91 file-not-open) set FILE STATUS but do not trigger AT END. The program then processes stale or empty record areas.
What to do instead
Declare a two-character FILE STATUS field for every file and test it after every OPEN, CLOSE, READ, WRITE, REWRITE, and DELETE. Handle at least the 00 success, 10 end-of-file, and 9x severe categories explicitly, and use INVALID KEY or a status test for indexed and relative files. Log or display unexpected statuses rather than continuing.
Opening an indexed file in the wrong mode (for example INPUT when a REWRITE is needed, or I-O when records must be added) and then misdiagnosing the resulting status code as a logic bug.
Candidates focus on the I/O verb and overlook that the OPEN mode determines which verbs are legal. REWRITE and DELETE require I-O or, for DELETE, I-O; WRITE of new records requires OUTPUT or EXTEND for sequential files and I-O or OUTPUT for indexed files. The failure surfaces as a status code far from the OPEN statement.
What to do instead
Map each required operation to the minimum OPEN mode before coding: INPUT for read-only, OUTPUT for create/overwrite, I-O for read plus update/delete, EXTEND for append. Check FILE STATUS immediately after OPEN and abort cleanly if the open fails, so the real cause is reported at the point of failure.
Using a subscript to access a table without validating it against the OCCURS count, and using the same data name for the table and its elementary item, so out-of-bounds access silently reads adjacent storage.
COBOL does not bounds-check subscripts at run time by default. Candidates assume the compiler or runtime will trap an out-of-range subscript, but the subscript is simply used as an offset from the table's base address. A subscript of zero or one past the last entry reads whatever storage follows, producing plausible but wrong data instead of an error.
What to do instead
Validate every computed subscript against 1 and the OCCURS value before use, and handle the invalid case explicitly. Prefer INDEXED BY with SET, SEARCH, and SEARCH ALL for indexed access, and define the table with a group item name wrapping the OCCURS so the table name and element name are distinct. Compile and test with bounds checking enabled if the compiler supports it.
Passing parameters to a subprogram with mismatched BY REFERENCE and BY CONTENT, or with a CALL literal that does not match the program-id, and then debugging the resulting corrupted values in the caller.
BY REFERENCE passes an address, so the subprogram can modify the caller's field even when the caller expected a copy; BY CONTENT passes a copy but the callee must declare it consistently. Candidates also forget that a CALL identifier resolves at run time and that a mismatch between the literal and PROGRAM-ID causes a load failure or a call to the wrong module.
What to do instead
State BY REFERENCE or BY CONTENT explicitly on both the CALL and the PROCEDURE DIVISION USING, and keep the parameter order and PICTUREs identical. Use CALL 'literal' matching the PROGRAM-ID exactly, check the returned value, and consider CANCEL after the final call if the subprogram must not retain WORKING-STORAGE state.
Diagnosing an abend or wrong result by editing the source and recompiling repeatedly without reading the compiler listing, the FILE STATUS, or the first failing statement, so the actual cause is masked by later changes.
Candidates treat debugging as trial-and-error editing. COBOL compilers emit valuable diagnostics in the listing (data-name cross-references, offset maps, truncation warnings, unreachable code), and run-time failures usually leave a status code or a specific failing statement. Changing several things at once destroys the evidence.
What to do instead
Change one thing at a time and keep a record. Read the compiler listing for warnings about truncated moves and undefined data names, use DISPLAY of key fields and FILE STATUS at paragraph boundaries, and use the debugger or a trace to find the first statement where state diverges from expectation. Reproduce with a minimal input before fixing.
Treating COBOL as unable to interoperate with modern systems and therefore ignoring character encoding, packed-decimal representation, and callable interfaces when integrating with Java, C, or web services.
Candidates assume COBOL data is plain text. In practice COMP-3 packed decimal, COMP binary fields, EBCDIC versus ASCII encoding, and fixed-length records all affect how data crosses a boundary. A JSON or REST integration that sends a COMP-3 field as characters, or that assumes UTF-8 where the file is EBCDIC, produces garbled or rejected data.
What to do instead
Define the interface contract explicitly: which fields are display, COMP, or COMP-3; the code page on both sides; and record lengths and padding. Convert packed and binary fields to display or numeric form before serialization, translate EBCDIC to ASCII/UTF-8 at the boundary, and test round-trips with representative values including negatives and maximums.