Once you can read a program’s structure, the next question is what actually lives inside its declaration area. RPGLE Program Structure Explained showed that a member has a section where variables are introduced before any executable logic runs. This lesson stays in that section and answers two separate questions: how do you declare a variable, and what decides where that variable can be used and how long it keeps its value?
Those really are two questions, not one. Declaring a variable fixes its name, its type, and its starting value. Its storage type fixes something else: the part of the program that can see the name, and whether the value survives from one call to the next. You can choose a good name and a sensible type and still put the variable in the wrong scope or give it the wrong lifetime, so it is worth keeping the two ideas apart from the start.
One clarification first. In this lesson, “storage type” means storage class – automatic, static, based, or external – not packed decimal, character, integer, or date. Those are data types, and how big they are and how they behave are the subject of the next lesson, ARTICLE-060, “RPGLE Data Types, Lengths, and Precision”. Here, a type is just the label a declaration must carry.
Estimated reading time: 12 to 15 minutes
Quick Summary
- A variable is named, typed storage whose value can change while the program runs. It is not a file field (the file defines that) and not a constant (that value never changes).
- New code declares a standalone variable with
DCL-S: a name, a type, a size where the type needs one, and optional keywords such asINZorLIKE.1 - Older members declare the same thing with a fixed-format
Dspecification, or occasionally with theDEFINEoperation. Recognizing that form is maintenance knowledge, not a new-code style.7 - Scope is visibility: a variable declared in the main section is global to the module, and a variable declared inside a subprocedure is local to that procedure.2
- Lifetime is persistence: local variables are automatic by default, so their storage is re-established and re-initialized on every call. The
STATICkeyword changes the lifetime to one instance that is kept across calls; it does not change the scope.3 4 - “Working storage” is an informal name for a program’s variable area, not an RPG keyword or a section you declare.
BASED,IMPORT, andEXPORTplace a variable’s storage somewhere other than ordinary local or global storage. Recognize them; later lessons cover them.
Concepts and Defaults at a Glance
| Concept | Practical direction | Guardrail |
|---|---|---|
Declare with DCL-S | Use DCL-S for standalone variables in fully free-form code | Fixed-format D specs are for reading legacy source, not for new code |
| Name states meaning | Give the variable a name a reader understands without a comment | Naming rules belong to RPGLE Naming Conventions and Code Organization |
| Give a known starting value | Prefer an explicit INZ; an un-INZed variable still has a type-dependent default5 | The list of per-type defaults belongs to ARTICLE-060 |
| Pick the type, then move on | Every declaration needs a type; choose an obvious one | Length, precision, conversion, and overflow are ARTICLE-060 |
| Choose the narrowest scope | Declare a variable inside the procedure that uses it unless it must be shared | The full rules of subprocedure scope belong to a later procedures lesson |
| Default to automatic lifetime | Let local variables reset on each call unless a value must persist3 | Describe automatic storage as per-invocation storage, not “a stack” |
Use STATIC deliberately | Use STATIC only when a value must survive between calls, and say why4 | STATIC changes lifetime, not visibility; it introduces hidden state |
Treat BASED and external storage as advanced | Recognize BASED, IMPORT, and EXPORT; do not reach for them yet8 | Pointers and cross-module storage are covered in later lessons |
| “Working storage” is a model | Use it to picture where values live | RPGLE has no WORKING-STORAGE section |
Reader Prerequisites
This lesson assumes you can already locate the declaration section of a member and tell it apart from executable logic, the skill built in RPGLE Program Structure Explained. It also assumes you can recognize the three RPGLE source forms – fixed-format, column-limited free-form, and fully free-form – at the level explained in RPGLE Specifications: Fixed, Free, and Fully Free. You do not need to have written a declaration before, and you do not need to know the RPGLE data-type catalogue; that is the next lesson.
Learning Outcomes
By the end of this lesson, you will be able to:
- explain that a variable is a named, typed region of storage whose value can change;
- declare a standalone field with
DCL-S, using a name, a type, a size, andINZwhere appropriate; - recognize a legacy
D-spec orDEFINEdeclaration well enough to maintain it; - separate scope (where a name is visible) from lifetime (how long a value lasts);
- describe global and local scope, and automatic and
STATIClifetime, and predict which values reset between calls; - explain “working storage” as an informal model rather than a language feature;
- recognize
BASED,IMPORT, andEXPORTwithout using them; and - choose a sensible default declaration and say why broad scope with a long lifetime is a maintenance risk.
A Variable Is Named, Typed Storage
A variable binds a name to a piece of storage that has a type. The type tells the compiler how many bytes to reserve and how to interpret them; the name is how your code refers to that storage; and the value is what the storage currently holds. Unlike a literal or a named constant, a variable’s value can change as the program runs.
Two things a variable is not. It is not a field defined by a database or device file; those fields get their names, types, and sizes from the file’s description. And it is not a constant, a name for a fixed value the program cannot change at run time, which a later lesson on constants and literals covers. A variable sits between those: named like a constant, writable like a file field.

The same value can be stored in different ways depending on where you declare the variable. A total that only one procedure needs can live inside that procedure and disappear when it returns. A total that the whole module shares has to live somewhere every procedure can see it. Those are scope and lifetime decisions, and the rest of this lesson is about making them on purpose.
Declaring a Standalone Field with DCL-S
In fully free-form RPGLE, a standalone variable is declared with DCL-S, followed by the name, the type, and any keywords.1 A “standalone” field is one that stands on its own, as opposed to a subfield inside a data structure.
**free
ctl-opt dftactgrp(*no) actgrp(*caller);
// name type keyword(s)
dcl-s customerCount int(10) inz(0);
dcl-s customerName char(30);
dcl-s runningTotal packed(11:2) inz(0);
dcl-s accountActive ind inz(*off);
dcl-s reviewDate date inz(*sys);
dcl-s previousTotal like(runningTotal);
Read one line at a time. customerCount is the name; int(10) is the type and size; inz(0) is a keyword that sets the starting value. customerName has a type and size but no INZ, so it starts at whatever default its type defines – the point here is that “no INZ” does not mean “no value”, it means “the type’s default value”. What that default actually is for each type is ARTICLE-060’s job; this lesson only asks you to notice that INZ is how you choose the starting value explicitly.5
The reviewDate line shows an INZ that is not zero or blank: inz(*sys) asks for the system date at the point the variable is initialized. The last line uses LIKE: at compile time previousTotal takes its type, length, and decimal positions from runningTotal, so editing runningTotal‘s declaration and recompiling updates previousTotal to match.6 LIKE is a declaration convenience; it does not link the two variables at run time, and it is not a way to build structures.
The type names in this example – int, char, packed, ind, date – appear only so the declarations are concrete. Choosing among them, and knowing their exact sizes and precision, is the subject of the next lesson. The complete file is stored at content/code/rpgle/article-059/standalone-declaration-anatomy.rpgle.
Reading Legacy Declarations: D Specs and DEFINE
Before DCL-S existed, standalone fields were declared on a fixed-format definition specification, where each entry sits in a fixed column range: the letter D in position 6, the name in positions 7 through 21, the definition-type entry (S for a standalone field) in positions 24 to 25, a right-justified length ending in position 39, a one-letter internal data type in position 40, decimal positions in positions 41 to 42, and keywords such as INZ from position 44 onward.7 You will also see the older DEFINE operation used to introduce a field in some legacy calculation code. Neither is something to write in new code; both are things to recognize when you open an existing member.
* Legacy fixed-format recognition excerpt only. Preserve every column.
*....+....1....+....2....+....3....+....4....+....5
D CustomerCount S 10I 0 INZ(0)
D CustomerName S 30A
D BalanceDue S 11P 2 INZ(0)
D AccountActive S 1N INZ(*OFF)
In that excerpt 10I 0 is a 10-digit integer, 30A is 30 characters, 11P 2 is packed decimal with 11 digits and 2 of them after the decimal point, and 1N is a one-character indicator. The same four declarations in modern form:
**free
dcl-s customerCount int(10) inz(0); // was: D ... 10I 0 INZ(0)
dcl-s customerName char(30); // was: D ... 30A
dcl-s balanceDue packed(11:2) inz(0); // was: D ... 11P 2 INZ(0)
dcl-s accountActive ind inz(*off); // was: D ... 1N INZ(*OFF)
The correspondence is line for line here: the name maps to the name, the length area maps to the size in parentheses, and the INZ keyword carries straight across. That is not a promise that every legacy declaration converts this cleanly – external descriptions, overlays, and length notations can complicate it – but for plain standalone fields the mapping is direct. Fixed-format positions carry meaning the compiler depends on, so treat the excerpt as read-only; the source-form recognition skill itself is covered in RPGLE Specifications: Fixed, Free, and Fully Free. Both files are stored at content/code/rpgle/article-059/legacy-d-spec-recognition.rpgle and content/code/rpgle/article-059/modern-dcl-s-equivalent.rpgle.
Scope: Where a Variable Can Be Used
Scope is the answer to “where can I refer to this name?” RPGLE has two levels you need at this stage.2
A variable declared in the main section of the module – the declaration area that RPGLE Program Structure Explained described – is global. Every procedure in that module can read and write it. “Global” here means the module, the unit that gets compiled together; it does not mean other programs can reach in and use it. Sharing a variable across separately compiled parts is a different mechanism, covered under EXPORT and IMPORT later.
A variable declared inside a subprocedure is local to that procedure. Code outside the procedure cannot see the name at all – not because it is protected, but because the name simply does not exist anywhere else. Two different procedures can each declare a local variable called count and they are unrelated.
The practical guidance is to declare a variable in the narrowest scope that works. If only one procedure uses a value, declare it in that procedure. A module full of global variables that any procedure can change is hard to reason about, because any line anywhere could be the one that changed the value you are looking at. The complete rules for nested and procedure-interface scope belong to a later lesson on procedures; this global-versus-local distinction is enough to work with here.
Lifetime: How Long a Value Lasts
Lifetime is a separate question from scope: “how long does this storage keep its value?”

A global variable has one instance that exists for the life of the program. It holds whatever was last written to it until something writes again.
A local variable is automatic by default. Automatic means the storage is established fresh each time the procedure is called and released when it returns, and its INZ value is applied again on every one of those calls.3 Nothing a procedure stores in an automatic variable carries over to the next call. This is usually what you want: each call starts clean.
The important point is that automatic versus static is independent of scope. An automatic local and a static local are both invisible outside their procedure. Changing the lifetime does not change who can see the variable.
Static Storage: Keeping a Value Between Calls
When a local variable genuinely needs to remember something between calls, the STATIC keyword gives it a single instance that is initialized once and kept for the life of the program, even though its scope is still local to the procedure.4
**free
ctl-opt dftactgrp(*no) actgrp(*caller) main(run);
dcl-s programName varchar(10) inz('ART059'); // global scope, whole-program lifetime
dcl-proc run;
bump();
bump();
bump();
end-proc;
dcl-proc bump;
dcl-s autoCount int(10) inz(0); // local + automatic: reset every call
dcl-s keptCount int(10) inz(0) static; // local + static: retained across calls
dcl-s line varchar(40);
autoCount += 1;
keptCount += 1;
line = programName + ' auto=' + %char(autoCount) + ' kept=' + %char(keptCount);
dsply line;
end-proc;
run calls bump three times. autoCount is automatic, so it is set back to 0 at the start of every call and is always 1 after the increment. keptCount is STATIC, so it is 0 only on the very first call and then climbs. Each call displays line, giving:
ART059 auto=1 kept=1
ART059 auto=1 kept=2
ART059 auto=1 kept=3
programName is declared in the main section, so both run and bump can see it; that is scope. autoCount and keptCount have the same local scope but different lifetimes; that is storage type.
A reasonable use of STATIC is a call counter, a cached lookup result, or a one-time setup flag. The cost is that the procedure’s behavior now depends on its own history: the same inputs can produce different results depending on how many times it has already run. That is hidden state, so when you use STATIC, say why in a comment. The full file is stored at content/code/rpgle/article-059/automatic-vs-static-storage.rpgle.
Working Storage: A Picture of Where Values Live
“Working storage” is a phrase you will hear applied to RPGLE, usually meaning “the program’s variable area”. As a mental model, picture the module’s global variables, plus the automatic storage each currently-running procedure has, plus any STATIC variables that persist alongside. That whole collection is what people loosely call working storage.
RPGLE has no WORKING-STORAGE SECTION the way COBOL does: no keyword, no declared region by that name. The term is borrowed shorthand for “where the variables are”. The model still earns its place because it explains the behavior you just saw – automatic values disappear when a procedure returns because that procedure’s automatic storage is released, while global and static values stay because their storage is not.
Storage That Lives Elsewhere: BASED, IMPORT, and EXPORT
Three keywords take a variable’s storage out of the ordinary local-or-global picture. You should recognize them now and leave the detail for later.
BASEDdeclares a variable whose storage is wherever a pointer says it is, rather than a slot the compiler reserves. The pointer is often set by a run-time allocation.8 Pointers and dynamic allocation are a later topic.EXPORTandIMPORTare a pair. A variable defined withEXPORTin one module has its storage shared by name with a variable defined withIMPORTin another module. This is how separately compiled parts can share a single variable, and it is covered alongside modules and service programs later in the cluster.
If you see any of these in existing code, note that the variable’s storage is defined somewhere other than the line in front of you, and move on; none of them is needed for ordinary declarations.
Choosing a Sensible Default
When you are not sure how to declare something, this is a safe starting point:
- Use
DCL-Swith a clear, meaningful name. - Give it an explicit
INZunless there is a reason not to. - Declare it in the narrowest scope that works – inside the procedure that uses it, if only one does.
- Let it be automatic. Reach for
STATIConly when a value must survive between calls, and add a comment saying why. - Do not reach for
BASED,IMPORT, orEXPORTto solve an ordinary declaration problem.
Two things that should prompt a second look in review: a global variable that several procedures write to, and a STATIC local with no comment explaining why it needs to persist.
Common Mistakes
- Treating “storage types” as data types and expecting a packed-versus-integer discussion here; that is ARTICLE-060.
- Thinking
STATICmakes a variable visible to other procedures. It changes lifetime, not scope. - Expecting a local automatic variable to remember its value between calls.
- Assuming an un-
INZed variable holds zero or blank without checking what its type’s default actually is. - Declaring everything in the main section because it always works, and ending up with wide mutable state.
- Using
STATICwith no comment explaining why the value must persist. - Reusing one unrelated variable for several purposes instead of declaring a clearly named field.
- Describing automatic storage as “the stack” as though that were documented RPG terminology.
- Believing RPGLE has a
WORKING-STORAGE SECTIONlike COBOL. - Copying fixed-format
Dspecs into new code instead of writingDCL-S. - Reaching for
BASEDor pointers to solve a plain declaration. - Confusing a variable with a file field or a named constant.
- Calling RPGLE or IBM i source “mainframe code”; the platform is IBM i.
FAQ
What is a variable in RPGLE?
A name bound to a piece of typed storage whose value can change while the program runs. The type fixes the size and interpretation of the storage; the name is how your code refers to it.
How do I declare a variable in RPGLE?
In fully free-form RPGLE, use DCL-S followed by the name, the type, and any keywords, for example dcl-s customerCount int(10) inz(0);.1 Older members use a fixed-format D specification for the same purpose.7
What does INZ do, and what value does a variable have without it?
INZ sets the starting value explicitly, such as inz(0) or inz(*sys).5 Without INZ, a variable still has a starting value: the default defined by its type. This lesson does not list those per-type defaults; ARTICLE-060 does.
What is the difference between automatic and static storage?
Automatic is the default for local variables: the storage is re-established and re-initialized on every call, so nothing carries over.3 STATIC gives the variable one instance that is initialized once and kept across calls.4 Both are still local in scope.
What is the difference between a global and a local variable?
A variable declared in the module’s main section is global: every procedure in that module can use it, and it lasts for the whole program. A variable declared inside a subprocedure is local: only that procedure can see the name.2
What does “working storage” mean in RPGLE?
It is an informal name for a program’s variable area – global storage, each running procedure’s automatic storage, and any static storage together. RPGLE has no WORKING-STORAGE section; the phrase is borrowed shorthand.
Do I need to know about BASED, IMPORT, or EXPORT yet?
Only enough to recognize them. BASED puts a variable’s storage where a pointer points; EXPORT and IMPORT share one variable’s storage across separately compiled modules.8 The details come with later lessons on pointers and on modules and service programs.
Key Takeaways
- A variable is named, typed storage. Declaring it decides the name, the type, and the starting value.
DCL-Sis the modern way to declare a standalone variable; fixed-formatDspecs andDEFINEare for reading legacy source.INZsets the starting value explicitly; without it, the type’s own default applies.- Scope and lifetime are independent. Scope is where the name is visible; lifetime is how long the value lasts.
- Local variables are automatic by default and reset on every call.
STATICkeeps one instance across calls but does not widen scope. - “Working storage” is a mental model for where values live, not a language feature.
BASED,IMPORT, andEXPORTput storage elsewhere; recognize them and leave the detail for later.
Continue Your Learning
- Previous: RPGLE Source Layout and Readability
- Current: RPGLE Variables and Storage Types
- Next: RPGLE Data Types, Lengths, and Precision (ARTICLE-060, planned in the canonical RPGLE cluster)
- Then: RPGLE Data Structures Explained (ARTICLE-061, planned in the canonical RPGLE cluster)
- Return to: RPGLE for Beginners: A Practical IBM i Learning Path
ARTICLE-060 takes the type slot that every declaration in this lesson had to fill and explains the choices: the numeric and character types, their lengths and precision, and how values convert between them. ARTICLE-061 then moves from single variables to grouped fields. Because neither is in the publishing tracker yet, this article names them as planned lessons rather than linking to them.
IBM Evidence Used
This lesson uses IBM i 7.4 as its primary baseline for definition-specification, scope, storage-duration, and keyword behavior, and IBM i 7.5 for the DCL-S free-form declaration statement. It has no IBM i 7.6 dependency.
- IBM’s free-form definition statement reference supports the
DCL-Sstandalone-field declaration syntax, what a standalone field is, and the keywords it accepts.1 - IBM’s definition-specification reference supports how the legacy fixed-format
Dspecification is laid out.7 - IBM’s scope-of-definitions reference supports the global-versus-local distinction for variables declared in the main section and in subprocedures.2
- IBM’s storage-of-definitions and
STATICreferences support automatic storage as the default for local variables andSTATICas a retained single instance.3 4 - IBM’s
INZandLIKEreferences support explicit initialization and defining a variable from an existing item.5 6 - IBM’s
BASEDreference supports the recognition-level description of pointer-based storage;IMPORTandEXPORTare named here only at recognition level.8
The recommendations in this lesson – prefer the narrowest scope, initialize explicitly, comment every STATIC – are editorial guidance for maintainable code, not IBM requirements.
References
- Free-Form Definition Statement — IBM
- Definition Specification — IBM
- Scope of Definitions — IBM
- Storage of Definitions — IBM
- STATIC (*ALLTHREAD) — IBM
- INZ (Initial Value) — IBM
- BASED (basing_pointer_name) — IBM
- LIKE (name) — IBM