The Stored-Program Computer

Imagine building a machine that can only run one program, wired directly into its circuits. To do anything new, engineers would rewire thousands of switches and cables by hand. This was once a process that took days. This was the reality of early computers like the ENIAC. It made "reprogramming" almost as expensive as building a new machine. The stored-program computer solved this by treating instructions as data. Both live in the same memory, addressed the same way. Both are manipulated by the same operations. This single idea is arguably the most consequential in computing history. It means a computer's behavior is not fixed in hardware. Instead, it is determined by a sequence of bits sitting in memory. These bits are ready to be fetched, interpreted, and executed. Because programs are just data, they can be loaded from disk. They can be transmitted over a network, or generated by other programs (compilers). They can even be modified while running. Every laptop, phone, and server you have ever touched follows this model. The big picture: a stored-program machine consists of a processor and a memory connected by a bus. Memory holds both the instructions that define a computation and the data that computation operates on. The processor does not know, a priori, which memory locations hold code and which hold numbers. It simply fetches whatever is at the address it is told to fetch. It interprets this according to context (as an instruction during fetch, as data during a load or store). This article explains precisely what the stored-program model is. It explains why it replaced hard-wired and plugboard designs. It shows how a program is laid out and executed within a shared memory space. It also explores the profound implications of letting code and data mingle in the same address space — implications both liberating and dangerous. It sets up the machinery of the Fetch-Decode-Execute Cycle and the Instruction Set Architecture as a Contract. Later articles develop these ideas in depth.

Key takeaways
  • The stored-program model keeps instructions and data in the same memory, addressed uniformly.
  • This is what makes software "soft": changing behavior means writing new bits to memory, not rewiring hardware.
  • A program counter (PC) tracks which memory address holds the next instruction to fetch.
  • Because code is data, programs can generate, modify, or overwrite other programs — power and danger in one.
  • The architecture is named after John von Neumann's 1945 EDVAC report, though credit is genuinely collective.
  • It underlies virtually every general-purpose computer, and directly motivates the fetch-decode-execute cycle and instruction set design.
The core insight

A stored-program computer treats instructions as ordinary data living in the same memory as the numbers they manipulate, so that changing a program is nothing more than changing memory contents.

The idea

A stored-program computer is a machine architecture in which both the instructions that define a computation and the data that computation processes reside in a single, shared, randomly addressable memory. The processor retrieves instructions from memory using an address held in a special register — the program counter (PC) — decodes the bit pattern it finds there into an operation, and executes it, possibly reading or writing other memory locations as data. Nothing in the memory hardware itself distinguishes "this byte is code" from "this byte is a number": that distinction exists only in how the processor is currently using the address it reads. This contrasts with earlier designs. Hard-wired or fixed-program computers had their logic built directly into circuits or plugboards — the "program" was literally the wiring. Plugboard-programmed machines like the original ENIAC (1945) required operators to physically rearrange cables and switches to change the computation, a process that could take a full day. The stored-program idea, formalized in the concept of von Neumann architecture, replaced physical rewiring with symbolic rewriting: to change what the machine does, you write different bit patterns into memory and reset the program counter to point at them. The key terminology to fix precisely: memory is an array of addressable storage cells, each holding a fixed-width unit (a byte or word); an instruction is a bit pattern that the processor's control unit knows how to decode into a micro-operation (add, load, branch, and so on); the program counter is a register holding the address of the next instruction to fetch; and von Neumann architecture specifically denotes the unified memory model, as opposed to the Harvard architecture, which physically separates instruction memory from data memory (used in many microcontrollers and cache designs today, but logically still "stored-program" in spirit since programs are loaded as data before execution begins). The essential unifying claim, sometimes stated as the stored-program principle, is this: a program is data, and therefore can be operated on like data — copied, transformed, generated by other programs, and stored persistently.

How it works

The mechanics of a stored-program machine rest on a small number of moving parts: a memory array addressed by integers, a set of registers inside the processor (including the PC), and a control unit that cycles through fetching, decoding, and executing instructions. Because instructions and data share the address space, the only thing that determines whether address 4096 is treated as code or as an integer is whether the PC currently points there, or whether some load/store instruction refers to it as an operand. Consider the layout of a simple program in memory. The compiler or assembler translates source code into a linear sequence of machine instructions, placing them at consecutive addresses starting from some base (say, address 0). Data — global variables, constants, the stack — occupies other regions of the same address space. When the machine starts, the PC is initialized to the address of the first instruction; execution proceeds by repeating a simple loop. ``` // The stored-program execution loop (conceptual, one processor cycle) PC ← START_ADDRESS // initialize program counter loop forever: instruction ← Memory[PC] // FETCH: read bits at address PC opcode, operands ← Decode(instruction) // DECODE: interpret the bit pattern switch opcode: case LOAD: reg[operands.dst] ← Memory[operands.addr] // read data from memory case STORE: Memory[operands.addr] ← reg[operands.src] // write data to memory case ADD: reg[operands.dst] ← reg[operands.a] + reg[operands.b] case BRANCH_IF_ZERO: if reg[operands.test] == 0: PC ← operands.target - INSTRUCTION_SIZE // will be advanced below case HALT: stop PC ← PC + INSTRUCTION_SIZE // advance to next instruction, unless branch overrode it ``` Two facts about this loop are the whole point of the article. First, `Memory[PC]` and `Memory[operands.addr]` are the same function — the same memory array, accessed via the same read mechanism — used in fundamentally different roles (fetching an instruction versus loading a data operand). Second, because `Memory` is writable, a running program can compute a new instruction and `STORE` it into a location that the PC will later reach, causing the machine to execute code that did not exist when the program began running. This is precisely how loaders, compilers, JIT (just-in-time) compilers, and — less benignly — self-modifying malware operate. A cleaner, more general trace of what "loading a program" means end-to-end: ``` function LoadAndRun(programBytes, dataBytes, entryPoint): // Step 1: place instructions into memory (as raw bytes / words) for i from 0 to length(programBytes) - 1: Memory[CODE_BASE + i] ← programBytes[i] // Step 2: place initial data into memory for i from 0 to length(dataBytes) - 1: Memory[DATA_BASE + i] ← dataBytes[i] // Step 3: point the program counter at the first instruction PC ← CODE_BASE + entryPoint // Step 4: run the fetch-decode-execute loop until HALT RunFetchDecodeExecuteLoop() ``` Notice that `LoadAndRun` never inspects `programBytes` to check "is this really valid code?" — it simply writes bytes into memory exactly as it would for data. Validity is enforced (if at all) by convention, by the operating system, or by hardware protections like the no-execute (NX) bit — not by any inherent property of the memory itself. This is the crux of both the power and the peril of the stored-program model.

Figure
Unified memory: code and data share one address space
Processor PC = 0x0104 Reg R1 = 7 Reg R2 = 3 Control Unit bus Unified Memory CODE region 0x0100: LOAD R1, [0x0300] 0x0104: ADD R1, R1, R2 ← PC 0x0108: STORE [0x0300], R1 0x010C: HALT DATA region 0x0300: value = 3 0x0304: counter = 0 Same bus, same address space, same read/write path.

Complexity & correctness

The stored-program model is not itself an algorithm with a running time; it is an architectural organizing principle. The relevant "complexity" claims are about the cost of the fetch-decode-execute cycle it enables, and about the cost of changing a program.

Per-instruction cost. Each iteration of the loop performs a bounded, constant number of memory accesses: one fetch of the instruction word, plus at most a small constant number of operand loads/stores (typically 0, 1, or 2 for RISC-style instruction sets). Assuming memory access is O(1)O(1) (true for a fixed-size random-access memory model, ignoring cache hierarchy effects), each instruction executes in O(1)O(1) time. A program of nn executed instructions therefore runs in O(n)O(n) time attributable to fetch-decode-execute overhead, on top of whatever the instructions themselves compute. This is the same accounting used to justify the running-time model ("unit-cost RAM model") used throughout algorithm analysis: the stored-program cycle is why that model is realistic for real machines, not just a convenient fiction.

Cost of reprogramming. This is where the stored-program model's efficiency claim is sharpest and easiest to justify. On a plugboard or hard-wired machine, changing the program requires physically altering Θ(W)\Theta(W) pieces of wiring/switch state, where WW is the wiring complexity of the previous configuration — historically this took hours to days, and scaled with program complexity, not program size in memory. On a stored-program machine, changing the program means overwriting kk words of memory, where kk is the number of instructions that differ, each write being O(1)O(1): total cost O(k)O(k), typically milliseconds to load a new binary from storage. This asymptotic gap — physical rewiring versus memory writes — is the actual engineering reason the idea mattered so much: it converts an O(W)O(W)-labor task requiring specialized technicians into an O(k)O(k)-time, fully automatable task (a `LOAD` program from disk).

Correctness / invariant argument. The correctness of the fetch-decode-execute loop rests on one invariant, which must hold at the top of every iteration: > Invariant: At the start of each cycle, `PC` holds the address of a valid instruction — that is, `Memory[PC]` is the intended next instruction of the running program, not arbitrary or unintended data.

  • Base case: the loader establishes the invariant by setting `PC ← entryPoint` after correctly placing the program's instructions in memory (as in `LoadAndRun` above).
  • Inductive step: each iteration either (a) increments `PC` by exactly `INSTRUCTION_SIZE`, which is correct precisely when instructions are packed contiguously and none was skipped or double-counted, or (b) executes a branch/jump that sets `PC` to a target address which the compiler/assembler guarantees is the start of a valid instruction (branch targets are always instruction boundaries by construction of the assembler/linker).
  • Termination / halting: the loop exits correctly if and when `HALT` is decoded, or, more generally, whenever the invariant is violated by an external event (invalid address, protection fault) that the hardware is expected to trap rather than silently execute garbage as an instruction.

The subtlety — and the honest tradeoff — is that the invariant is a program-level guarantee, not a hardware-level one. Nothing in the raw memory array prevents `PC` from being set (accidentally through a bug, or maliciously through an exploit) to an address holding attacker-controlled data rather than intended code. The stored-program model's uniformity is exactly what makes this failure mode possible: because instructions and data are indistinguishable at the memory level, the invariant can only be enforced by external mechanisms — compilers that emit well-formed jump targets, operating systems that mark memory regions non-executable, and hardware features like the NX/XD bit. This is the fundamental tradeoff of the model: uniform, flexible addressing in exchange for the loss of a hardware-enforced code/data boundary.

Worked example

Trace a tiny stored-program computation: compute `7 + 3` and store the result, then trace what happens if a data value is accidentally interpreted as an instruction.

Setup. Memory (word-addressed, 4 bytes/word) initially:

AddressContentsRegion
0x0100`LOAD R1, [0x0300]`code
0x0104`LOAD R2, [0x0304]`code
0x0108`ADD R3, R1, R2`code
0x010C`STORE [0x0308], R3`code
0x0110`HALT`code
0x0300`7`data
0x0304`3`data
0x0308`0` (uninitialized)data

Execution trace, with `PC` initialized to `0x0100`:

StepPCFetch → DecodeActionRegisters afterMemory changed
10x0100`LOAD R1,[0x0300]`read Memory[0x0300]=7 into R1R1=7—
20x0104`LOAD R2,[0x0304]`read Memory[0x0304]=3 into R2R1=7, R2=3—
30x0108`ADD R3,R1,R2`R3 ← 7+3R1=7, R2=3, R3=10—
40x010C`STORE [0x0308],R3`write R3 into Memory[0x0308](unchanged)Memory[0x0308]=10
50x0110`HALT`stop fetch-decode-execute loop——

Each row demonstrates the invariant: `PC` always names a legitimate instruction address, and it advances by exactly one instruction width (4 bytes) except where a branch would override it (none here).

Now the cautionary trace. Suppose a bug sets `PC ← 0x0300` instead of `0x0100` (e.g., an off-by-one in computing a jump target, or a corrupted return address). The same fetch logic runs:

StepPCMemory[PC] (raw bits)"Decoded" as instruction
10x0300the bit pattern for integer `7`whatever opcode those bits happen to encode — almost certainly nonsense or a wildly wrong operation

The hardware has no way to know that `7` was "meant" as data. It will cheerfully decode those bits as an instruction and execute whatever operation they happen to specify — this is exactly the class of bug (and, when induced deliberately, exploit) that arises from treating code and data uniformly. The example demonstrates concretely why the stored-program model requires disciplined bookkeeping by software, since the hardware alone cannot recover the "intended" boundary between code and data once it has been lost.

Common pitfalls & traps

PitfallWhy it happensConsequence
Confusing Harvard architecture with "not stored-program"Harvard machines physically separate instruction/data memoryStill stored-program in spirit (code is loaded as data, then treated as instructions); the split is a hardware optimization for bandwidth, not a rejection of the principle
Assuming hardware enforces code/data separationThe unified-memory model has no built-in type tag on bytesJumping to a bad address executes arbitrary data as code — undefined, often catastrophic behavior
Off-by-one in `PC` advancementForgetting instruction width, or advancing before a branch decision is appliedExecutes the wrong instruction, or skips/repeats one, silently corrupting control flow
Treating self-modifying code as always dangerousHistorical necessity (subroutine returns before index registers existed) vs. modern misuseConfuses legitimate JIT-compilation techniques with malicious code injection — both exploit the same underlying mechanism
Ignoring the loader stepAssuming a program "just runs" without asking how bytes got into memoryMisses that `LOAD`, linking, and relocation are themselves stored-program operations worth understanding

Pioneers & history

The stored-program idea crystallized in the mid-1940s at the intersection of several groups working independently and in close correspondence, making attribution famously contentious. J. Presper Eckert and John Mauchly, builders of ENIAC, conceived key elements of a stored-program successor (the EDVAC) in discussions during 1944–45. John von Neumann, brought in as a consultant, wrote up these ideas in the "First Draft of a Report on the EDVAC" (1945), circulated widely under his name alone — which is why the architecture bears his name, though the underlying credit is genuinely shared. In Britain, Alan Turing had already described, in his 1936 paper on computable numbers, a universal machine whose "instructions" and "tape" occupied a single formal object — a theoretical anticipation of the same unifying idea, later realized in hardware by the Manchester Baby (1948) and EDSAC (1949).

Origins · 1 minJohn von Neumann and how this was discovered
JV
John von Neumann
1903–1957 · Mid-1940s, Institute for Advanced Study and the EDVAC project

A mathematician and polymath whose 1945 EDVAC report formalized the unified memory model, giving the architecture its name — even as Eckert, Mauchly, and Turing's earlier theoretical work make the true history a collaborative one.

History
1936
— Alan Turing describes a universal machine whose instructions and data occupy a single tape.
1945
— John von Neumann circulates the "First Draft of a Report on the EDVAC," naming the unified stored-program model.
1948
— The Manchester Baby runs the first stored-program instruction from electronic memory.
1949
— EDSAC, built by Maurice Wilkes at Cambridge, becomes the first practical stored-program computer to run regular programs.
1950
s — Commercial stored-program machines (UNIVAC, IBM 701) bring the model into industrial and scientific computing.

Where this matters

Every general-purpose processor sold today — from smartphone application processors to cloud server CPUs — is a stored-program machine at its core, even when it uses Harvard-style split caches for instructions and data internally for performance. Operating systems rely on the model to load programs: a `.exe` or ELF binary on disk is nothing but bytes, copied into memory exactly as `LoadAndRun` describes, with the loader responsible for placing the entry point correctly. Compilers and JIT engines (in Java's JVM, JavaScript's V8, Python's CPython) exploit the model directly, generating machine code at runtime and writing it into executable memory pages for immediate execution — a controlled, intentional use of self-modifying-code-like mechanics. Conversely, security architecture spends enormous engineering effort re-imposing a code/data boundary the hardware does not naturally provide: the NX/DEP (Data Execution Prevention) bit, address space layout randomization (ASLR), and control-flow integrity checks all exist specifically to compensate for the stored-program model's uniform treatment of memory. Understanding this article is the direct prerequisite for the Fetch-Decode-Execute Cycle (which formalizes the loop shown here) and for Instruction Set Architecture as a Contract (which specifies exactly what bit patterns the control unit is allowed to decode as valid instructions) — and it depends on already understanding Binary Representation and Memory Basics, since "instructions are data" only makes sense once you know both are just patterns of bits in an addressable array.

Ask the tutor: How does the von Neumann bottleneck affect modern CPU performance, and how do split caches address it?

Is the "von Neumann architecture" the same thing as the "stored-program computer"?

Essentially yes, with a fine distinction. "Stored-program computer" describes the principle: instructions live in writable memory alongside data, so programs can be loaded, generated, or modified like any other data. "Von Neumann architecture" is the specific name given to the implementation of that principle using a single unified memory accessed by one bus, as described in von Neumann's 1945 EDVAC report. In casual usage the two terms are near-synonyms, and most textbooks use them interchangeably. The technical distinction matters mainly when contrasting with Harvard architecture, which is still stored-program (code is loaded from storage as data) but physically separates instruction and data memory for bandwidth reasons, without abandoning the underlying principle.

Why does sharing memory between code and data matter for performance, not just flexibility?

Sharing memory means a single bus and single set of address decoders serve both instruction fetches and data accesses, which is simpler and cheaper to build than duplicated memory systems — a major reason early stored-program machines were feasible at all with limited vacuum-tube or magnetic-core memory. The tradeoff is contention: an instruction fetch and a data load can compete for the same memory port on the same cycle, a bottleneck often called the "von Neumann bottleneck." Modern processors mitigate this with split L1 instruction and data caches (a Harvard-style optimization layered atop a von Neumann main memory), getting the flexibility of unified memory with much of the bandwidth of separated memory.

How does self-modifying code relate to the stored-program principle?

Self-modifying code is the most literal expression of the principle: a running program computes new instruction bytes and writes them into memory locations the program counter will later reach, causing the machine to execute code that did not exist when the program started. Historically, early machines used this technique routinely — for example, to implement indexed addressing before index registers existed in hardware, programmers would modify an instruction's address field directly. Today it survives in disciplined forms like just-in-time (JIT) compilation (V8, the JVM) and dynamic linking, and in undisciplined, dangerous forms like code-injection exploits, both relying on exactly the same hardware mechanism: memory is memory, whether you call it code or data.

Why can't the hardware just tag each memory word as "code" or "data" to prevent bugs and exploits?

Some systems do add exactly this kind of tagging — the NX (no-execute) bit marks memory pages as non-executable, and some research architectures use fine-grained tagged memory. But a hardware tag on every word adds storage overhead, complicates legitimate uses like JIT compilation (which needs to write code and then execute it), and doesn't eliminate the deeper issue: the tag has to be set correctly by software at some point, which reintroduces the same trust problem one level up. The stored-program model's flexibility and its vulnerability are two faces of the same design decision; NX bits, ASLR, and control-flow integrity are practical compromises, not a full return to hardware-enforced separation.

Does every modern CPU still strictly follow the stored-program model?

Yes, at the architectural level visible to a programmer: every mainstream CPU (x86, ARM, RISC-V) presents a single logical address space in which instructions and data both reside and both can, in principle, be read or written by ordinary load/store operations. Internally, performance engineering adds complexity — separate L1 instruction and data caches, instruction prefetch buffers, out-of-order execution — but these are implementation details layered on top of the stored-program abstraction, not replacements for it. A program can always, in principle, inspect or modify its own code in memory, which is the defining test of whether the model still holds.

What historical alternative did the stored-program model replace, and why was it abandoned?

It replaced plugboard and switch-based programming, exemplified by the original ENIAC (1945), where the "program" was physically encoded in cable connections and switch settings rather than in memory. Reconfiguring ENIAC for a new calculation could take a team of operators the better part of a day, because the labor scaled with the complexity of the wiring itself. The stored-program model was abandoned in favor of memory-resident programs precisely because writing bits to memory is orders of magnitude faster and fully automatable — a program can now load another program in milliseconds, something no plugboard rewiring could approach.

How does this concept connect to instruction set design and the fetch-decode-execute cycle?

The stored-program model is the precondition for both. An Instruction Set Architecture as a Contract specifies exactly which bit patterns, when fetched from memory, the control unit will decode into which operations — this contract only makes sense once you accept that instructions are bit patterns sitting in ordinary memory, indistinguishable from data except by convention. The Fetch-Decode-Execute Cycle is simply the operational loop — fetch from the address in PC, decode, execute, advance PC — that a stored-program machine runs continuously; this article's execution loop pseudocode is a preview of that fuller treatment.

Where this leads