Diagnosing APL Off-by-One Errors from the ⎕IO Index Origin
Find and fix APL off-by-one indexing errors caused by the ⎕IO index origin setting, with checks, scoped fixes, and escalation criteria.
21 Dec 2025, 03:38 UTC

The Problem: Asking for the First Element and Getting the Second
You reference `⍵[1]` expecting the first item of an array, but APL returns the second item, or raises `INDEX ERROR` on a position that should exist. The likely cause is ⎕IO, the index origin system variable. This guide shows how to confirm whether ⎕IO is responsible, trace where it changed, and apply a fix that does not silently break other code.
Useful takeaway: Check ⎕IO before rewriting indexing logic. If you change it, save and restore the previous value, then verify with the expression that originally failed.
What ⎕IO Controls
⎕IO is a system variable that defines the starting index for array operations. When ⎕IO is 1, the first element of a vector is at index 1. When ⎕IO is 0, the first element is at index 0. It affects primitives such as `⍳` (index generator), `↑` (take), and `⌷` (indexing), as well as any user code that hard-codes numeric positions. Defaults differ between APL dialects and workspaces, so a value that works in one session may not apply in another.
Recognizable Condition
- `⍵[1]` returns the second element, or fails with `INDEX ERROR` on a single-element array.
- `1↑⍳5` returns `2` instead of `1` when ⎕IO is 0.
- A loop or index calculation is consistently off by one position.
- Code works in one workspace but fails after migration or after loading a different application.
Cause and Diagnostic Table
| Observed symptom | Likely cause | First check |
|---|---|---|
| `⍵[1]` returns element 2 | ⎕IO is 0; code assumes 1-based indexing | Display `⎕IO` in the failing session |
| `INDEX ERROR` at the last valid position | Hard-coded upper bound assumes 1-based indexing | Inspect the failing function source |
| Works interactively, fails inside a function | A function locally assigns ⎕IO and does not restore it | Search the workspace for `⎕IO←` |
| Fails only after loading a legacy workspace | Migrated workspace uses a different ⎕IO default | Compare `⎕IO` across workspaces |
Ordered Checks
- Show the current value. At the APL session prompt, enter `⎕IO`. Where to run: interactive session. Required permissions: none. Expected result: `0` or `1`. Risk: none; this is read-only.
- Reproduce with a minimal expression. Enter `1↑⍳5`. With ⎕IO set to 0, this returns the second item of `⍳5`; with ⎕IO set to 1, it returns the first item. If the result does not match your code's assumption, ⎕IO is implicated. Risk: none.
- Search for assignments. In the function editor, or with `⎕NR '<function-name>'`, look for lines containing `⎕IO←`. Where to run: workspace editor. Required permissions: read access to the workspace. Expected result: no unexpected assignments in code that should be origin-neutral.
- Inspect hard-coded indices. Open the failing function and look for numeric literals used directly as array indices, such as `⍵[3]` or `⍳10`. Note whether the author clearly intended 0-based or 1-based positions.
Fixes Tied to Each Finding
Finding: ⎕IO is 0 but the application expects 1
Set the origin at the start of the workspace or application entry point:
⎕IO←1
This is a global state change. It affects every function and primitive in the session, so apply it only when the whole codebase is intended to run with the same origin.
Finding: only one function needs a different origin
Save the caller's value, set the origin you need, run the code, then restore:
oldIO←⎕IO ⋄ ⎕IO←1 ⋄ result←⍵[3] ⋄ ⎕IO←oldIO
If an error occurs between the assignment and the restore, ⎕IO stays changed. In APL systems with structured error handling, wrap the body in a protected block so the restore runs even on failure:
∇ result←RunWithOrigin code;oldIO
oldIO←⎕IO
:Try
⎕IO←1
result←⍎code
:Finally
⎕IO←oldIO
:EndTry
∇
Replace `⍎code` with the actual expression or function call. The `:Finally` section executes whether the `:Try` block succeeds or signals an error.
Finding: mixed ⎕IO usage across the workspace
Refactor toward origin-independent indexing instead of toggling ⎕IO. For example, the third element of `⍵` can be written as:
third←⍵[⎕IO+2]
When ⎕IO is 0, `⎕IO+2` is index 2 (the third element). When ⎕IO is 1, it is index 3 (also the third element). Apply the same pattern to `⍳`, `↑`, and other origin-sensitive primitives by computing offsets from `⎕IO` rather than hard-coding positions.
Rollback and State Restore
Because assigning ⎕IO changes workspace state, treat any session-level change as temporary unless you intend a permanent default. After testing, restart the APL session or explicitly restore the previous value to confirm no other code depends on the modified origin.
Escalation Criteria
- ⎕IO is consistent across sessions but off-by-one errors persist: the bug is in the algorithm, not the index origin.
- Multiple workspaces or teams require conflicting defaults: do not force one global value; refactor shared code to be origin-independent.
- Production code cannot be stopped or the workspace re-saved: schedule a maintenance window before changing a global default.
- User-defined operators leave ⎕IO altered after an error: fix the error-handling path before deploying the fix.
Limitations and Practical Verification
Changing ⎕IO globally can alter the behavior of unrelated functions and primitives. Some legacy code is difficult to refactor because it assumes a specific origin throughout. After any fix:
- Run the expression that originally failed and confirm it returns the intended element.
- Run a regression subset of the workspace to catch other origin-sensitive code.
- Use `)SI` (the state indicator) to confirm no suspended functions remain on the call stack with an altered ⎕IO.
If the corrected expression returns the expected position and the regression subset passes, the diagnostic is complete.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.