COBOL Subprograms: CALL, USING, BY REFERENCE, and LINKAGE SECTION

COBOL modular programming uses the CALL statement to invoke subprograms, passing data between caller and called program through a defined parameter list. This is how large mainframe systems are composed from reusable business logic modules — a CICS front-end calls an account validation subprogram, which calls a database access subprogram, which calls a logging subprogram. Understanding CALL mechanics is essential for both writing new modules and maintaining existing ones.
Subprogram Anatomy
A COBOL subprogram looks almost identical to a main program, with two key differences: it receives data through a LINKAGE SECTION, and it returns with GOBACK (not STOP RUN):
IDENTIFICATION DIVISION.
PROGRAM-ID. VALACCT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-LOCAL-BALANCE PIC S9(11)V99 COMP-3.
01 WS-VALIDATION-RESULT PIC X(1).
LINKAGE SECTION.
01 LS-PARMS.
05 LS-ACCOUNT-ID PIC X(10).
05 LS-AMOUNT PIC S9(9)V99 COMP-3.
05 LS-RETURN-CODE PIC S9(4) COMP.
05 LS-ERROR-MSG PIC X(50).
PROCEDURE DIVISION USING LS-PARMS.
MAIN-LOGIC.
MOVE ZERO TO LS-RETURN-CODE
MOVE SPACES TO LS-ERROR-MSG
PERFORM VALIDATE-ACCOUNT-ID
IF LS-RETURN-CODE = ZERO
PERFORM VALIDATE-AMOUNT
END-IF
GOBACK.
VALIDATE-ACCOUNT-ID.
IF LS-ACCOUNT-ID = SPACES
MOVE -1 TO LS-RETURN-CODE
MOVE 'ACCOUNT ID IS REQUIRED' TO LS-ERROR-MSG
END-IF.
VALIDATE-AMOUNT.
IF LS-AMOUNT <= ZERO
MOVE -2 TO LS-RETURN-CODE
MOVE 'AMOUNT MUST BE POSITIVE' TO LS-ERROR-MSG
END-IF.The LINKAGE SECTION defines the parameter structure. The PROCEDURE DIVISION USING clause lists the parameters. GOBACK returns control to the calling program.
CALL Statement
Static CALL (Literal Program Name)
WORKING-STORAGE SECTION.
01 WS-CALL-PARMS.
05 WS-ACCOUNT-ID PIC X(10).
05 WS-AMOUNT PIC S9(9)V99 COMP-3.
05 WS-RETURN-CODE PIC S9(4) COMP.
05 WS-ERROR-MSG PIC X(50).
PROCEDURE DIVISION.
MOVE 'ACC0012345' TO WS-ACCOUNT-ID
MOVE 500.00 TO WS-AMOUNT
CALL 'VALACCT' USING WS-CALL-PARMS
IF WS-RETURN-CODE NOT = ZERO
DISPLAY 'VALIDATION FAILED: ' WS-ERROR-MSG
PERFORM HANDLE-VALIDATION-ERROR
END-IF.CALL 'VALACCT' uses a literal program name — this is a static call. The linker resolves the reference at link-edit time and both programs share a single load module.
Dynamic CALL (Identifier Program Name)
WORKING-STORAGE SECTION.
01 WS-PROG-NAME PIC X(8).
PROCEDURE DIVISION.
MOVE 'VALACCT ' TO WS-PROG-NAME
CALL WS-PROG-NAME USING WS-CALL-PARMS.When the program name is stored in a field (WS-PROG-NAME), the CALL is dynamic. The runtime loads the program from the load library at first call. Subsequent CALLs within the same run unit find the program already resident in memory.
Dynamic CALL enables runtime polymorphism — you can change WS-PROG-NAME based on a configuration table to call different validation routines without recompiling the calling program.
Passing Parameters
BY REFERENCE (Default)
BY REFERENCE passes the address of the data item. The called program maps the LINKAGE SECTION directly onto the caller's memory:
CALL 'VALACCT' USING BY REFERENCE WS-CALL-PARMS.
*> 'BY REFERENCE' is the default — this is equivalent:
CALL 'VALACCT' USING WS-CALL-PARMS.When the subprogram modifies LS-RETURN-CODE, it is modifying WS-CALL-PARMS's return code field directly. No copying occurs — the change is immediately visible to the calling program after CALL returns.
BY CONTENT
BY CONTENT passes a copy of the data. The called program receives its own copy; modifications do not affect the caller:
CALL 'CALCINTEREST' USING
BY CONTENT WS-PRINCIPAL
BY CONTENT WS-INTEREST-RATE
BY REFERENCE WS-CALCULATED-INTEREST.Here, PRINCIPAL and RATE are passed as copies (the subprogram cannot accidentally change them), but the RESULT field is passed by reference so the subprogram can return the calculated value.
BY VALUE
BY VALUE passes the actual value for scalar items — used primarily when interfacing with C programs or system routines:
CALL 'CGETENV' USING
BY REFERENCE WS-ENV-VAR-NAME
BY VALUE FUNCTION LENGTH(WS-ENV-VAR-NAME)
BY REFERENCE WS-ENV-VAR-VALUE.Passing Individual Fields vs Group Items
You can pass individual fields or entire group records:
*> Pass entire group record (most common):
CALL 'UPDATEBAL' USING WS-UPDATE-PARMS.
*> Pass individual fields:
CALL 'UPDATEBAL' USING
WS-ACCOUNT-ID
WS-TRANSACTION-AMOUNT
WS-NEW-BALANCE
WS-RETURN-CODE.The LINKAGE SECTION of UPDATEBAL must match the layout — either as a single group with the same substructure, or as four separate 01-level items in the same order:
LINKAGE SECTION.
*> Matching the individual-field CALL:
01 LS-ACCOUNT-ID PIC X(10).
01 LS-TRANSACTION-AMOUNT PIC S9(9)V99 COMP-3.
01 LS-NEW-BALANCE PIC S9(11)V99 COMP-3.
01 LS-RETURN-CODE PIC S9(4) COMP.
PROCEDURE DIVISION USING
LS-ACCOUNT-ID
LS-TRANSACTION-AMOUNT
LS-NEW-BALANCE
LS-RETURN-CODE.CALL Exception Handling
ON EXCEPTION / ON OVERFLOW
CALL 'EXTPROC' USING WS-PARMS
ON EXCEPTION
DISPLAY 'EXTPROC NOT FOUND OR LOAD FAILED'
MOVE -99 TO WS-RETURN-CODE
NOT ON EXCEPTION
PERFORM CHECK-RETURN-CODE
END-CALL.ON EXCEPTION fires when:
- The called program cannot be found in the load library (dynamic CALL)
- The program failed to load due to authorization or storage issues
Without ON EXCEPTION, a load failure causes an S806 abend.
CANCEL Statement
CANCEL releases a dynamically loaded program from storage, forcing it to be reloaded on next CALL. This also reinitializes its WORKING-STORAGE:
CANCEL 'VALACCT'.
CANCEL WS-PROG-NAME.Use CANCEL when:
- You need the subprogram's WORKING-STORAGE reinitialized (it normally retains values between calls)
- You are done with a subprogram and want to release its storage
- You need to pick up a new version of a subprogram that was updated in the load library
Do NOT CANCEL a program that is currently executing (including any program in your call chain above you).
Nested CALL Chains
COBOL supports call chains of arbitrary depth. A typical production pattern:
MAINPGM → CALL 'TRXPROCESS'
→ CALL 'VALIDATETRX'
→ CALL 'DBREAD' (database access)
→ CALL 'DATECHEK' (date validation)
→ CALL 'UPDATEDB'
→ CALL 'DBWRITE'
→ CALL 'LOGAUDIT'Each called program gets its own WORKING-STORAGE. LINKAGE SECTION items point back to the caller's storage — there is no stack copying for BY REFERENCE parameters.
Returning Multiple Values
COBOL has no native return value — all output goes through LINKAGE SECTION fields. The standard convention is:
LINKAGE SECTION.
01 LS-SERVICE-PARMS.
05 LS-INPUT-ACCOUNT-ID PIC X(10). *> INPUT
05 LS-INPUT-AMOUNT PIC S9(9)V99 COMP-3. *> INPUT
05 LS-OUTPUT-NEW-BALANCE PIC S9(11)V99 COMP-3. *> OUTPUT
05 LS-OUTPUT-RETURN-CODE PIC S9(4) COMP. *> OUTPUT: 0=ok, negative=error
05 LS-OUTPUT-ERROR-MSG PIC X(80). *> OUTPUT: populated on errorDocument clearly which fields are INPUT (set by caller before CALL), OUTPUT (set by subprogram before GOBACK), or both (caller sets, subprogram may update).
Complete Example: Account Update Service
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCTUPD.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DB-STATUS PIC S9(4) COMP VALUE ZERO.
01 WS-ACCOUNT-BALANCE PIC S9(11)V99 COMP-3.
LINKAGE SECTION.
01 LS-REQUEST.
05 LS-ACCOUNT-ID PIC X(10).
05 LS-AMOUNT PIC S9(9)V99 COMP-3.
05 LS-TRANS-TYPE PIC X(2).
88 LT-CREDIT VALUE 'CR'.
88 LT-DEBIT VALUE 'DR'.
05 LS-RETURN-CODE PIC S9(4) COMP.
05 LS-MESSAGE PIC X(80).
PROCEDURE DIVISION USING LS-REQUEST.
MAIN-LOGIC.
MOVE ZERO TO LS-RETURN-CODE
MOVE SPACES TO LS-MESSAGE
PERFORM FETCH-ACCOUNT-BALANCE
IF LS-RETURN-CODE = ZERO
PERFORM APPLY-TRANSACTION
END-IF
GOBACK.
FETCH-ACCOUNT-BALANCE.
CALL 'DBFETCH' USING LS-ACCOUNT-ID
WS-ACCOUNT-BALANCE
WS-DB-STATUS
IF WS-DB-STATUS NOT = ZERO
MOVE -1 TO LS-RETURN-CODE
MOVE 'ACCOUNT NOT FOUND' TO LS-MESSAGE
END-IF.
APPLY-TRANSACTION.
EVALUATE TRUE
WHEN LT-CREDIT
ADD LS-AMOUNT TO WS-ACCOUNT-BALANCE
WHEN LT-DEBIT
IF LS-AMOUNT > WS-ACCOUNT-BALANCE
MOVE -2 TO LS-RETURN-CODE
MOVE 'INSUFFICIENT FUNDS' TO LS-MESSAGE
GOBACK
END-IF
SUBTRACT LS-AMOUNT FROM WS-ACCOUNT-BALANCE
WHEN OTHER
MOVE -3 TO LS-RETURN-CODE
MOVE 'UNKNOWN TRANSACTION TYPE' TO LS-MESSAGE
GOBACK
END-EVALUATE
CALL 'DBUPDATE' USING LS-ACCOUNT-ID
WS-ACCOUNT-BALANCE
WS-DB-STATUS
IF WS-DB-STATUS NOT = ZERO
MOVE -4 TO LS-RETURN-CODE
MOVE 'DATABASE UPDATE FAILED' TO LS-MESSAGE
END-IF.Next Steps
Subprograms establish the call structure. The next essential tool is copybooks — COPY statements that share record definitions, SQL declarations, and data layouts across programs without duplication. See COBOL Copybooks, or return to the COBOL Mastery course.
