Building Stacks and Queues with Immutable Arrays
No push, no pop, and the array itself can't be changed — and you can still build stacks and queues? You can, and the result is downright elegant. This page assembles Lesson 16's four-piece kit (.Length, grabs that can come up empty, Slice, +=) into two classic structures, and gives you a feel for "arrays are values — swap the whole row" in real combat.
1. A Change of Mindset: Don't Edit the Container — Replace It
A stack is last-in, first-out, like stacking plates: new plates go on top, and you take from the top too. A queue is first-in, first-out, like lining up for loadout: enter at the tail, leave from the head. Elsewhere these are built with in-place moves like push / pop / dequeue; Verse arrays are values without a single in-place move available — no matter. Flip the mindset and it clicks: every operation builds a new row and replaces the entire old row in the variable.
Set up the mapping first: push / enqueue = tail concatenation set Xs += array{X}; pop = read the last item Xs[Xs.Length - 1], then cut off the tail with Slice[0, Length - 1]; dequeue = read the first item Xs[0], then keep the rest with Slice[1, Length]. Epic's official docs devote a page to this game plan, "Stacks and Queues in Verse"; the implementations below are the standard treatment of the topic — for fine details, defer to the official page.
2. The Stack: An Undo System
The most classic stack in games is undo: every action gets pushed onto the stack; the undo key pops the most recent one. Look at the Pop function — it grabs a slot and then calls Slice, and both steps might not succeed, so the whole function's nameplate carries <transacts><decides> and it gets called with square brackets: Pop[]. When the stack is empty, Stack[Stack.Length - 1] is Stack[-1], guaranteed to come up empty, and the whole Pop fails to go through with it — the rule "can't pop an empty stack" grew straight out of the machinery without you writing a single defensive check.
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
undo_stack_device := class(creative_device):
# Stack: last in, first out; the tail is the top
var Stack:[]string = array{}
Push(Item:string):void =
set Stack += array{Item}
Pop()<transacts><decides>:string =
Top := Stack[Stack.Length - 1]
set Stack = Stack.Slice[0, Stack.Length - 1]
Top
OnBegin<override>()<suspends>:void =
Push("Break Wall")
Push("Build Ramp")
Push("Place Door")
if (Undone := Pop[]):
Print("Undid: {Undone}")
Print("{Stack.Length} steps left on the stack")
if (A := Pop[], B := Pop[]):
Print("Undid two more: {A} and {B}")
if (Empty := Pop[]):
Print("Never gets here")
else:
Print("Stack is empty, Pop failed — no crash, just a failure")
Run the mental simulation: after three Pushes the stack holds Break Wall, Build Ramp, Place Door; the first Pop[] ejects the finale, Place Door; then A and B pop Build Ramp and Break Wall in turn; the last Pop[] faces an empty stack, crisply fails to go through, and swerves into else. One hidden bonus: in if (A := Pop[], B := Pop[]), if the B step doesn't go through, the item A already popped gets undone wholesale — stuffed back onto the stack. A "half-popped" middle state simply cannot occur (see the deep-dive page for the why).
3. The Queue: A Matchmaking Waiting Room
The queue flips direction: entry stays at the tail, but exit moves to the head of the line. The classic scenario is matchmaking — first player in line, first into the match:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
match_queue_device := class(creative_device):
# Queue: first in, first out; the head is slot 0
var Queue:[]string = array{}
Enqueue(Item:string):void =
set Queue += array{Item}
Dequeue()<transacts><decides>:string =
First := Queue[0]
set Queue = Queue.Slice[1, Queue.Length]
First
OnBegin<override>()<suspends>:void =
Enqueue("Bubbles")
Enqueue("Dopey")
if (Next := Dequeue[]):
Print("{Next} enters the match")
Print("Still in line: {Queue.Length}")
Dequeue's two steps: Queue[0] reads the head of the line, Slice[1, Queue.Length] keeps everyone from slot 1 to the end — precisely "everyone but the head". On an empty queue, Queue[0] comes up empty and the whole Dequeue fails to go through — not one defensive check needed, same as the stack. There is a price, admittedly: every dequeue builds the row anew from scratch, which isn't the leanest approach when the line gets very long — but at the scale of island logic, clarity beats everything.
4. Pop Quiz
The stack is empty and you call Pop[]. What happens?
After Dequeue runs, what happened to the original Queue array itself?
Sources & Further Reading
The topic of this page comes from Epic's official documentation, "Stacks and Queues in Verse" (dev.epicgames.com ↗); the implementations shown are the standard treatment of the topic — for exact details, defer to the official page. To dig further into the "failure as a business rule" style, head to this lesson's deep-dive, "Why Out-of-Bounds Doesn't Throw".