COBOL COBOL 2014, GnuCOBOL 3.x ·
✓ verified by execution on 2026-08-16
When building a large COBOL system, putting everything into a single massive program quickly becomes unmaintainable. Modern—and even legacy—COBOL applications rely on modular design to manage complexity. This lesson explores how to structure large applications, pass data securely, and maintain shared layouts using copybooks.
Modular Execution with CALL
The CALL statement transfers control from one object program to another within the run unit. This is the cornerstone of COBOL modularity. When a program is called, the system begins execution at the first instruction in the subprogram’s PROCEDURE DIVISION.
To pass data, the calling program lists variables in the USING clause, and the called program declares matching variables in its LINKAGE SECTION. The LINKAGE SECTION in the called program must describe the data items passed by the calling program—it does not allocate its own memory, but maps directly to the caller’s memory.
cobol✓ verified output
>>SOURCE FORMAT IS FREEIDENTIFICATION DIVISION.PROGRAM-ID. MAINPROG.DATA DIVISION.WORKING-STORAGE SECTION.01 WS-MSG PIC X(20) VALUE 'MAIN PROGRAM'.PROCEDURE DIVISION. DISPLAY 'IN ' WS-MSG. CALL 'SUBPROG' USING WS-MSG. DISPLAY 'BACK IN ' WS-MSG. STOP RUN.END PROGRAM MAINPROG.IDENTIFICATION DIVISION.PROGRAM-ID. SUBPROG.DATA DIVISION.LINKAGE SECTION.01 LS-MSG PIC X(20).PROCEDURE DIVISION USING LS-MSG. DISPLAY 'IN SUBPROGRAM'. MOVE 'MODIFIED' TO LS-MSG. EXIT PROGRAM.END PROGRAM SUBPROG.
Output
IN MAIN PROGRAM
IN SUBPROGRAM
BACK IN MODIFIED
Your output
Because COBOL passes parameters BY REFERENCE by default, the memory address of WS-MSG is shared. The output will show that the subprogram’s modification alters the original variable in the main program.
Protecting Data: BY CONTENT
If you don’t want the subprogram to alter your original data, you must explicitly pass the parameter BY CONTENT. This forces the system to create a temporary copy of the variable for the subprogram to use.
cobol✓ verified output
>>SOURCE FORMAT IS FREEIDENTIFICATION DIVISION.PROGRAM-ID. MAINPGM2.DATA DIVISION.WORKING-STORAGE SECTION.01 WS-DATA PIC X(10) VALUE 'ORIGINAL'.PROCEDURE DIVISION. CALL 'SUBPGM2' USING BY CONTENT WS-DATA. DISPLAY WS-DATA. STOP RUN.END PROGRAM MAINPGM2.IDENTIFICATION DIVISION.PROGRAM-ID. SUBPGM2.DATA DIVISION.LINKAGE SECTION.01 LS-DATA PIC X(10).PROCEDURE DIVISION USING LS-DATA. MOVE 'CHANGED' TO LS-DATA. EXIT PROGRAM.END PROGRAM SUBPGM2.
Output
ORIGINAL
Your output
In this case, the MAINPGM2 program safely displays ORIGINAL even after the subprogram attempts to alter the data.
Dynamic vs. Static Linking
If you provide a string literal in the CALL statement (e.g., CALL 'SUBPROG'), the program is often statically linked (depending on the compiler flags). However, if the CALL identifier format is used, the program is called dynamically.
cobol✓ verified output
>>SOURCE FORMAT IS FREEIDENTIFICATION DIVISION.PROGRAM-ID. MAINPGM4.DATA DIVISION.WORKING-STORAGE SECTION.01 WS-PGM-NAME PIC X(8) VALUE 'SUBPGM4'.PROCEDURE DIVISION. CALL WS-PGM-NAME. STOP RUN.END PROGRAM MAINPGM4.IDENTIFICATION DIVISION.PROGRAM-ID. SUBPGM4.PROCEDURE DIVISION. DISPLAY 'DYNAMIC CALL'. EXIT PROGRAM.END PROGRAM SUBPGM4.
Output
DYNAMIC CALL
Your output
You can also call a program without using any arguments at all if no data needs to be passed.
Scope in Nested Programs
A nested program is contained entirely within a parent program’s END PROGRAM bounds. Nested programs are compiled into a single load module together.
By default, data defined in a parent program is completely isolated from its nested children. However, the GLOBAL clause specifies that a data-name is available to the program that declares it and to every program contained within that program.
Predict the outputcobol
Read the code. What exactly will it print? Commit to an answer before you look.
>>SOURCE FORMAT IS FREEIDENTIFICATION DIVISION. PROGRAM-ID. M.DATA DIVISION.WORKING-STORAGE SECTION.01 N PIC 99 VALUE 10 GLOBAL.PROCEDURE DIVISION. CALL 'S'. CALL 'S'. DISPLAY N STOP RUN.IDENTIFICATION DIVISION. PROGRAM-ID. S.PROCEDURE DIVISION. ADD 5 TO N EXIT PROGRAM.END PROGRAM S.END PROGRAM M.
Output
20
You predicted
Shared Definitions via COPY
Rather than duplicating data layouts (such as 01 level records) across many programs, large systems use COPY books. A COPY statement physically inserts the contents of a library file into your source code at compile time. It acts as a literal compile-time text inclusion mechanism, not a runtime module import.
Level numbers define the hierarchy of data within a record description. 01 defines the top-level record, while subsequent numbers define fields.
cobol✓ verified output
>>SOURCE FORMAT IS FREEIDENTIFICATION DIVISION.PROGRAM-ID. MAINPGM6.DATA DIVISION.WORKING-STORAGE SECTION.01 WS-LOC. 05 WS-SUB PIC X(5) VALUE 'LVL05'.PROCEDURE DIVISION. DISPLAY WS-SUB. STOP RUN.END PROGRAM MAINPGM6.
Output
LVL05
Your output
Handling Edge Cases
A major edge case when architecting systems is LINKAGE SECTION size mismatches. If the main program passes a 5-byte data item, but the subprogram defines the linkage item as 15 bytes, the subprogram may read or write past the actual allocated memory bounds, causing silent data corruption or an ABEND.
Additionally, standard COBOL programs cannot be called recursively unless the PROGRAM-ID includes the RECURSIVE clause; doing so triggers a runtime error.
Check yourself
By default, how does COBOL pass parameters to a called subprogram?
Reveal answer
— COBOL defaults to passing parameters BY REFERENCE, meaning the subprogram operates on the exact same memory address as the caller.
What happens if a nested program declares a variable in its WORKING-STORAGE without any special clauses?
Reveal answer
The program will fail to compile. — By default, variables are isolated. You must use the GLOBAL clause to make a variable accessible to nested programs.
What does the COPY statement do in COBOL?
Reveal answer
It copies data from one variable to another in memory. — COPY is a literal text inclusion at compile time, similar to a C macro or preprocessor include.
Why must you be careful about the size of items in the LINKAGE SECTION?
Reveal answer
Because LINKAGE items are automatically initialized to spaces. — The LINKAGE SECTION only describes data passed by the caller. If you define a 15-byte item for a 5-byte parameter, the subprogram will read or overwrite adjacent memory.
Challenges
Challenge 1 +15 XP
The subprogram is modifying WS-IMPORTANT-DATA, which breaks the main program's logic. Fix the CALL statement so that the subprogram receives a copy of the data and cannot modify the original.
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 "SAFE\n"
Need a hint? (−25% XP)
Use the BY CONTENT phrase in your CALL statement.
Show solution (0 XP)
>>SOURCE FORMAT IS FREEIDENTIFICATION DIVISION.PROGRAM-ID. MAIN-PGM.DATA DIVISION.WORKING-STORAGE SECTION.01 WS-IMPORTANT-DATA PIC X(10) VALUE 'SAFE'.PROCEDURE DIVISION. CALL 'BAD-SUB' USING BY CONTENT WS-IMPORTANT-DATA. DISPLAY WS-IMPORTANT-DATA. STOP RUN.END PROGRAM MAIN-PGM.IDENTIFICATION DIVISION.PROGRAM-ID. BAD-SUB.DATA DIVISION.LINKAGE SECTION.01 LS-DATA PIC X(10).PROCEDURE DIVISION USING LS-DATA. MOVE 'RUINED' TO LS-DATA. EXIT PROGRAM.END PROGRAM BAD-SUB.
🐞 Bug Hunt+15 XP
The subprogram expects a 15-character string, but the main program is only passing 5 characters. This mismatch can cause memory corruption. Fix the main program's WORKING-STORAGE so it matches the subprogram's expected length, and pad the initial value with spaces so the total length is 15.
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 "HELLO \n"
Need a hint? (−25% XP)
Change the PIC clause in the main program from X(5) to X(15) and pad the VALUE with 10 spaces.
Show solution (0 XP)
>>SOURCE FORMAT IS FREEIDENTIFICATION DIVISION.PROGRAM-ID. MAIN-SIZE.DATA DIVISION.WORKING-STORAGE SECTION.01 WS-DATA PIC X(15) VALUE 'HELLO '.PROCEDURE DIVISION. CALL 'SUB-SIZE' USING WS-DATA. STOP RUN.END PROGRAM MAIN-SIZE.IDENTIFICATION DIVISION.PROGRAM-ID. SUB-SIZE.DATA DIVISION.LINKAGE SECTION.01 LS-DATA PIC X(15).PROCEDURE DIVISION USING LS-DATA. DISPLAY LS-DATA. EXIT PROGRAM.END PROGRAM SUB-SIZE.