COBOL 102: Building a To-Do List and Escaping the Modern Web
If you look up a tutorial for any modern JavaScript framework—React, Vue, Svelte—the "Hello World" project is always a To-Do list.
To build it, the modern web developer downloads Node.js, spins up a package manager, installs Webpack, configures a Virtual DOM, and pulls down 400MB of nested dependencies. All of this just to save a few strings of text and check if a date has passed. It is the absolute pinnacle of the Cobra Effect we talked about recently: engineering complexity instead of engineering solutions.
Let's do it the way the banks do it. Let's build a To-Do list in COBOL.
In the last post, I mentioned that mainframes hate interactive prompts, and that their true power lies in file processing. Today, we are going to bridge that gap. We will build an interactive local program using GnuCOBOL that asks for input, but saves and reads the tasks using raw, enterprise-grade Sequential File I/O. We will also add a feature to check if a task is overdue based on the system date.
1. The Setup: Defining the File
In COBOL, you don't just open a file stream on the fly. You have to declare the physical file mapping in the ENVIRONMENT DIVISION and describe the exact byte layout of the file in the DATA DIVISION.
We are using LINE SEQUENTIAL organization. This means the file is saved as standard text with line breaks, meaning you can open the resulting todos.dat file in vim or notepad and read it directly.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
* OPTIONAL means it won't crash if the file doesn't exist yet
SELECT OPTIONAL TODO-FILE ASSIGN TO "todos.dat"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD TODO-FILE.
01 TODO-RECORD.
05 TODO-DUE-DATE PIC 9(8).
05 TODO-TEXT PIC X(50).
Notice the layout: 8 bytes for the date (YYYYMMDD), and 50 bytes for the text. No JSON overhead. No XML tags. Just exactly 58 bytes of data per record.
2. The Logic: Handling Time and I/O
To check if a task is overdue, we need to know the current date. COBOL has a built-in function called CURRENT-DATE that returns a 21-character string containing the date, time, and timezone. We only care about the first 8 characters (YYYYMMDD).
Here is the complete program. Save this as todo.cbl.
IDENTIFICATION DIVISION.
PROGRAM-ID. TODO-APP.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT OPTIONAL TODO-FILE ASSIGN TO "todos.dat"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD TODO-FILE.
01 TODO-RECORD.
05 TODO-DUE-DATE PIC 9(8).
05 TODO-TEXT PIC X(50).
WORKING-STORAGE SECTION.
01 WS-CHOICE PIC 9 VALUE 0.
01 WS-EOF PIC X VALUE 'N'.
01 WS-CURRENT-DATE PIC 9(8).
01 WS-TEMP-DATE-TIME PIC X(21).
01 WS-OVERDUE-MSG PIC X(10) VALUE SPACES.
PROCEDURE DIVISION.
MAIN-PARAGRAPH.
* Extract the current YYYYMMDD from the system
MOVE FUNCTION CURRENT-DATE TO WS-TEMP-DATE-TIME.
MOVE WS-TEMP-DATE-TIME(1:8) TO WS-CURRENT-DATE.
PERFORM UNTIL WS-CHOICE = 3
DISPLAY " "
DISPLAY "--- COBOL TO-DO LIST ---"
DISPLAY "1. ADD A TASK"
DISPLAY "2. VIEW TASKS"
DISPLAY "3. EXIT"
DISPLAY "CHOICE: " WITH NO ADVANCING
ACCEPT WS-CHOICE
IF WS-CHOICE = 1
PERFORM ADD-TASK
ELSE IF WS-CHOICE = 2
PERFORM VIEW-TASKS
END-IF
END-PERFORM.
DISPLAY "SYSTEM HALTED. GOODBYE.".
STOP RUN.
ADD-TASK.
* OPEN EXTEND appends to the file instead of overwriting it
OPEN EXTEND TODO-FILE.
DISPLAY "ENTER DUE DATE (YYYYMMDD): " WITH NO ADVANCING.
ACCEPT TODO-DUE-DATE.
DISPLAY "ENTER TASK DESCRIPTION: " WITH NO ADVANCING.
ACCEPT TODO-TEXT.
WRITE TODO-RECORD.
CLOSE TODO-FILE.
DISPLAY "[+] TASK SAVED TO DISK.".
VIEW-TASKS.
DISPLAY " "
DISPLAY "--- YOUR TASKS ---".
OPEN INPUT TODO-FILE.
MOVE 'N' TO WS-EOF.
* The classic COBOL read loop
PERFORM UNTIL WS-EOF = 'Y'
READ TODO-FILE
AT END
MOVE 'Y' TO WS-EOF
NOT AT END
IF TODO-DUE-DATE < WS-CURRENT-DATE
MOVE "[OVERDUE]" TO WS-OVERDUE-MSG
ELSE
MOVE " " TO WS-OVERDUE-MSG
END-IF
DISPLAY TODO-TEXT " " WS-OVERDUE-MSG
END-READ
END-PERFORM.
CLOSE TODO-FILE.
3. Compiling and Running
Compile it using GnuCOBOL just like we did in the last tutorial:
cobc -x -o todo todo.cbl
./todo
When you add a task, the OPEN EXTEND command grabs the file and positions the pointer at the very end so you can safely append data.
When you view your tasks, OPEN INPUT locks the file for reading. The READ statement pulls exactly 58 bytes from the disk, maps them directly into the memory layout we defined in the FD section, and executes the logic. Because dates are formatted strictly as YYYYMMDD, checking if a task is overdue is a simple mathematical less-than comparison (<). No complex Date-Object parsing required.
This is what the interaction looks like in your terminal:
--- COBOL TO-DO LIST ---
1. ADD A TASK
2. VIEW TASKS
3. EXIT
CHOICE: 1
ENTER DUE DATE (YYYYMMDD): 20260521
ENTER TASK DESCRIPTION: Update Papers for ID Card
[+] TASK SAVED TO DISK.
--- COBOL TO-DO LIST ---
1. ADD A TASK
2. VIEW TASKS
3. EXIT
CHOICE: 2
--- YOUR TASKS ---
Update Papers for ID Card
--- COBOL TO-DO LIST ---
1. ADD A TASK
2. VIEW TASKS
3. EXIT
CHOICE: 3
SYSTEM HALTED. GOODBYE.
The Beauty of Sequential Files
If you add a few tasks and then exit the program, type cat todos.dat into your Linux or macOS terminal.
You won't see a serialized SQLite blob or a nested JSON tree. You will see raw, perfectly aligned ASCII text. This is how the global financial system processes your credit card transactions every night—reading fixed-width records line by line, at blistering speeds, with absolute deterministic reliability.
Next time, we will strip away the interactive menu entirely and build a true batch processor that reads a file, processes thousands of calculations, and generates an enterprise report.