Nested OCCURS

COBOL COBOL 2014 (ISO/IEC 1989:2014), built with GnuCOBOL 3.x ยท โœ“ verified by execution on 2026-08-16

When working with complex data, a single list of items is often not enough. You might need to represent a grid, a coordinate system, or a matrix. In many modern programming languages, you would create a 2D array by defining an array of arrays or using syntax like int[][] grid. COBOL approaches this differently due to its hierarchical memory structure.

To create a multi-dimensional table in COBOL, you do not define multiple dimensions on a single line. Instead, you nest OCCURS clauses across different group levels.

Defining a Multi-Dimensional Table

In COBOL, you create a 2D table by defining a group item with an OCCURS clause, and then placing another OCCURS clause on a subordinate item within that group. When the compiler sees this, it multiplies the occurrences to reserve a single, contiguous block of memory.

cobol โœ“ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. TWO-D.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  GRID.
           05 T-ROW OCCURS 2 TIMES.
              10 T-COL OCCURS 2 TIMES.
                 15 CELL PIC 9.
       PROCEDURE DIVISION.
           MOVE 1 TO CELL(1 1)
           MOVE 2 TO CELL(1 2)
           MOVE 3 TO CELL(2 1)
           MOVE 4 TO CELL(2 2)
           DISPLAY "R1C2: " CELL(1 2)
           STOP RUN.
Output
R1C2: 2

If you look closely at the data division, T-COL is nested inside T-ROW. Since T-ROW occurs 2 times, and T-COL occurs 2 times within each row, the compiler reserves memory for a total of 4 CELL items.

Because OCCURS cannot be used on an 01 level item, the multi-dimensional structure must always begin at a subordinate level (like 05). You can nest up to 7 dimensions in standard COBOL, though anything beyond 3 dimensions is rarely seen in practice.

Memory Layout

Under the hood, COBOL stores this 2D table sequentially. There are no pointers or separate objects. The data is laid out strictly row-by-row in memory.

01 GRID (Contiguous Memory) 05 T-ROW(1) 10(1 1) 10(1 2) 05 T-ROW(2) 10(2 1) 10(2 2)

When you access CELL(2 1), the compiler simply calculates the offset: it jumps past the entire first T-ROW, then accesses the first T-COL within the second row. If you try to access a boundary outside the declared OCCURS (such as CELL(3 1)), COBOL calculates the offset as if the third row existed. Without boundary checks enabled during compilation, this will silently read or corrupt the adjacent memory immediately following the table.

Accessing Data with Subscripts

In C-family languages, you might access a 2D array by chaining brackets, like array[1][2]. In COBOL, all subscripts for a multi-dimensional table are grouped together inside a single set of parentheses.

The subscripts correspond directly to the nested OCCURS levels, reading from the outermost group to the innermost.

Predict the output cobol

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

       IDENTIFICATION DIVISION.
       PROGRAM-ID. INDEX-2D.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  GRID.
           05 T-ROW OCCURS 2 TIMES INDEXED BY R-IDX.
              10 T-COL OCCURS 2 TIMES INDEXED BY C-IDX.
                 15 CELL PIC 9.
       PROCEDURE DIVISION.
           MOVE 5 TO CELL(1 1). MOVE 6 TO CELL(1 2).
           MOVE 7 TO CELL(2 1). MOVE 8 TO CELL(2 2).
           SET R-IDX TO 2
           SET C-IDX TO 1
           DISPLAY "VAL: " CELL(R-IDX C-IDX)
           STOP RUN.
Output
VAL: 7

Notice that the subscripts must be separated by at least one space, for example: CELL(1 2). Modern compilers might forgive a comma (CELL(1, 2)), but standard COBOL syntax dictates the space. You can use numeric literals, variables, or indexes (via INDEXED BY) just as you would with a 1-dimensional table.

Iterating Over Multi-Dimensional Tables

To process every element in a multi-dimensional table, you simply nest PERFORM VARYING loops. The outer loop will iterate through the rows, while the inner loop iterates through the columns.

cobol โœ“ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. SUM-2D.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  GRID.
           05 T-ROW OCCURS 2 TIMES.
              10 T-COL OCCURS 2 TIMES.
                 15 CELL PIC 9.
       01  I PIC 9 VALUE 1.
       01  J PIC 9 VALUE 1.
       01  TOTAL PIC 99 VALUE 0.
       PROCEDURE DIVISION.
           MOVE 1 TO CELL(1 1). MOVE 2 TO CELL(1 2).
           MOVE 3 TO CELL(2 1). MOVE 4 TO CELL(2 2).
           PERFORM VARYING I FROM 1 BY 1 UNTIL I > 2
               PERFORM VARYING J FROM 1 BY 1 UNTIL J > 2
                   ADD CELL(I J) TO TOTAL
               END-PERFORM
           END-PERFORM
           DISPLAY "TOTAL: " TOTAL
           STOP RUN.
Output
TOTAL: 10

Because the memory is completely contiguous, iterating row-by-row perfectly matches the way the data lies in memory. This is generally the most cache-friendly and performant way to traverse large COBOL tables.

Check yourself

How do you declare a multi-dimensional table in COBOL?

Reveal answer

By nesting OCCURS clauses within subordinate group levels. โ€” COBOL requires you to nest OCCURS clauses. A subordinate level (e.g., 10) with an OCCURS clause inside a group level (e.g., 05) that also has an OCCURS clause creates a multi-dimensional table.

Which of the following is the correct syntax for accessing an element in a 2D COBOL table?

Reveal answer

CELL(1 2) โ€” COBOL requires subscripts to be inside a single set of parentheses and separated by spaces, like CELL(1 2). While some modern compilers allow commas followed by spaces, the standard format is space-separated.

What happens in GnuCOBOL by default (without boundary check flags) if you access a table element outside of its declared OCCURS size?

Reveal answer

It silently corrupts adjacent memory. โ€” By default, COBOL allocates a contiguous block of memory for tables. Out-of-bounds access simply calculates an offset into adjacent memory, silently reading from or overwriting it.

Challenges

๐Ÿž Bug Hunt +20 XP

Fix the table definition so that it correctly defines a 2-dimensional grid. Currently, both dimensions are defined at the same level.

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 "X\n"
Show solution (0 XP)
IDENTIFICATION DIVISION.
PROGRAM-ID. CHAL1.
DATA DIVISION.
WORKING-STORAGE SECTION.
01  THE-GRID.
    05 T-ROW OCCURS 3 TIMES.
       10 T-COL OCCURS 3 TIMES.
          15 CELL PIC X.
PROCEDURE DIVISION.
    MOVE "X" TO CELL(2 2)
    DISPLAY CELL(2 2)
    STOP RUN.

๐Ÿž Bug Hunt +20 XP

Fix the subscript syntax used to access the 2D table. The program currently fails to compile.

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 "5\n"
Show solution (0 XP)
IDENTIFICATION DIVISION.
PROGRAM-ID. CHAL2.
DATA DIVISION.
WORKING-STORAGE SECTION.
01  MAP-DATA.
    05 X-COORD OCCURS 2 TIMES.
       10 Y-COORD OCCURS 2 TIMES.
          15 VALUE-AT PIC 9.
PROCEDURE DIVISION.
    MOVE 5 TO VALUE-AT(1 2)
    DISPLAY VALUE-AT(1 2)
    STOP RUN.