An assembler hits a forward reference — a jump to a label defined later — before it knows the label’s address. The fix is two passes: pass one walks every instruction accumulating addresses and records each label in a symbol table; pass two walks again, now emitting bytes and patching every operand that referenced a label. Two linear passes turn forward references from impossible into trivial.
A program branches forward to L2, defined three instructions later. Pass one sums instruction sizes to place L2; pass two patches the branch operand. Watch the symbol table fill and the operand resolve: live demo
“You need two passes for an assembler.” — not strictly. A one-pass assembler works by backpatching: emit the branch with a blank operand, keep a fix-up list, and fill it when the label appears. Two passes is the simplest correct design, not the only one. cited
The symbol table is the whole trick: a name → address map built in one walk, consulted in the next. It is the assembler’s half of the same job the linker finishes across files. classical / folklore
Pass one’s address arithmetic and pass two’s patch run on the canonical compiler: