Stack Operations and the LIFO Principle
A stack can be implemented with an array or with a linked list, and both versions expose the same push, pop, and isEmpty operations.
Try it: Stack and Queue
The difference between a stack (last in, first out) and a queue (first in, first out).
How it works
- A stack adds (push) and removes (pop) at the same end, the top — the last item added leaves first.
- A queue adds (enqueue) at the back and removes (dequeue) from the front — the first item added leaves first.
- peek / front look at the next item without removing it.
- Adding to a full structure is an overflow; removing from an empty one is an underflow.
Default run (3 steps): Stack (bottom → top): 4, 7. 2 of 8 slots used. Choose an operation. Example: push 9. … Stack is now: 4, 7, 9. Size 3/8.
Simplified: Fixed capacity of 8 items so overflow can be shown; Python lists and collections.deque grow as needed.
Loading the simulation…
Testing an Upside-Down Belief
A stack is an abstract data type that follows the Last-In-First-Out principle: the item added most recently is the first one removed. Because a stack is defined only by its operations, the same idea can be built in more than one way. This article looks at two concrete implementations of a stack of ints, one built from an array and one built from a linked list, and shows exactly how each one tracks its top.
Many learners arrive with a specific belief about the array version. The usual picture of an array stack is drawn upside down, so it seems natural that the top of the stack must sit at the front of the array, at index 0. Keep that belief in mind as you read. The rest of the article checks it against the actual definitions.
What do you think happens?
In an array-based stack holding four items, which index holds the top item: index 0, or index 3 (the last used slot)?
Reveal answer
Index 3. The top item sits at position top minus 1, not at index 0.
Index 0 is the bottom of the stack. The source is explicit that position 0 is the bottom and position top-1 is the top, so the upside-down drawing does not move the top to index 0.
Where the Top Lives in an Array Stack
The number of items on an array-based stack changes over time, so the array has many slots that may or may not be in use at any moment. A counter, conventionally named top, records how many of those slots are in use. If top is the counter, the items live in positions 0, 1, up to top-1. The item in position 0 is at the bottom of the stack, and the item in position top-1 is at the top.
The diagram shows why the counter is not simply the index of the newest item. It is the count of items in use. Because the first slot is index 0, that same number is also the index of the next free slot. That double role is why the top item sits at top-1 rather than at top. Once you hold this picture, the upside-down drawing stops being confusing: the bottom and top are determined by positions 0 and top-1, whichever way the array is drawn on paper.
Three pieces make up the logic of the array implementation apart from growth. The first is the counter itself. The second is the guard in pop: if top is 0, the stack is empty, and the method throws an IllegalStateException with the message that you cannot pop from an empty stack. The third is isEmpty, which returns true exactly when top equals 0. Because push and pop both work through the counter, the array never needs to be searched. Each operation touches a fixed position computed from top.
Tracing Push, Pop, and isEmpty
In the array version, push places the new item at position top and then adds 1 to top. Pop reverses the order: it reads the item at position top-1, subtracts 1 from top, and returns the item it read. The next diagram follows a short sequence of operations and shows what the top, the size, and isEmpty look like after each step.
Pushing A, B, C, then popping, then pushing D
Starting from an empty array stack with top = 0, perform these operations in order: push A, push B, push C, pop, push D, pop, pop, pop. Record the returned value of each pop and the final value of isEmpty.
- 1push A
A is stored at position top, which is position 0. Then top becomes 1.
- 2push B
B is stored at position 1. Then top becomes 2.
- 3push C
C is stored at position 2. Then top becomes 3.
- 4pop (first)
Read position top-1, which is position 2, so the value is C. Subtract 1 from top, so top becomes 2. Return C.
- 5push D
D is stored at position top, which is position 2. This overwrites the old C. Nothing had to be erased, because the counter already marked position 2 as free. Then top becomes 3.
- 6pop (second)
Read position 2, which holds D. Top becomes 2. Return D.
- 7pop (third)
Read position 1, which holds B. Top becomes 1. Return B.
- 8pop (fourth)
Read position 0, which holds A. Top becomes 0. Return A.
- 9isEmpty
top equals 0, so
isEmptyreturns true.
Pops return C, D, B, A in that order, and isEmpty returns true. The most recently pushed value still in the stack is always the one removed first.
What do you think happens?
Starting from an empty stack, you push 3, then push 5, then pop. What value does the pop return?
Reveal answer
5
The stack follows Last-In-First-Out, so the item added most recently is removed first. 5 was pushed after 3, so pop returns 5 and leaves 3 on the stack.
How a Full Array Grows
A fixed-size array puts a limit on how many items the stack can hold. If that limit is unwanted, the source suggests the dynamic array technique. Before each push, the implementation checks whether top equals the length of the items array. If it does, the array is full. The code then creates a new, larger array and copies the current stack items into it. The source resizes by doubling the array's size, using Arrays.copyOf with twice the current length. The new item is stored in the next available spot, and top goes up by one as usual.
The resize step changes the storage but not the stack. The four existing items keep their order from bottom to top, and top still counts them. Only the container grows. This is why the abstract stack operations can stay the same while the array underneath is replaced. Callers of push, pop, and isEmpty never see the copy happen.
The Linked List Version
The linked list implementation reaches the same abstract behavior by a different route. Here the top of the stack is the node at the head of the list. A private static nested class named Node holds an int item and a reference next to the following node. The stack stores a single pointer, top, which points to the head node. If top is null, the stack is empty.
Push creates a new node, stores the new item in it, and points its next reference at the old top. Then top is moved to the new node. Pop saves the item from the head node, moves top to the second node, and returns the saved item. The source explains why this is a good choice: adding and removing nodes at the front of a linked list is much easier than inserting or deleting nodes in the middle. A stack only ever works at one end, so the front is the only place it needs.
Pushing 3 and 8, then popping on a linked list stack
Starting from an empty linked-list stack where top is null, push 3, push 8, then pop. Describe the head node after each step and state the returned value.
- 1push 3
Create a node holding 3. Its next reference points at the old top, which is null. Move top to this node. The head is now the node holding 3.
- 2push 8
Create a node holding 8. Its next reference points at the node holding 3. Move top to the new node. The head is now the node holding 8, and it names the node holding 3 beneath it.
- 3pop
Save the item 8 from the head node. Move top to the second node, which holds 3. Return the saved 8.
The pop returns 8, and the head is now the node holding 3. The stack is not empty, because top is not null.
In the array version, the top of the stack is found by arithmetic on the counter. In the linked list version, it is found by following the head pointer. Both designs present the same stack behavior to the caller, which is the point of defining a stack by its operations rather than by its storage.
Mistakes Beginners Make with Index 0
Common mistake Treating index 0 as the top of the array stack
What it looks likeWith items A, B, C in positions 0 to 2 and top = 3, the learner says the top item is A because it sits first in the array.
Why it failsPosition 0 is the bottom of the stack. The top item sits at position top-1, which here is position 2 holding C.
Do this insteadFind the top with top-1, not with index 0. Pop reads position top-1, subtracts 1 from top, and returns the value it read.
Common mistake Pushing to position top-1 instead of position top
What it looks likeWith top = 2, the learner writes the new item into position 1, overwriting B.
Why it failstop is the count of items in use and also the index of the next free slot. The new item belongs at position top, which is position 2 here.
Do this insteadPush stores the item at position top first, then adds 1 to top.
Common mistake Assuming pop must erase the removed value
What it looks likeAfter popping C from a stack with top = 3, the learner expects position 2 to be cleared before the next push.
Why it failsPop only lowers the counter. The next push simply writes over position 2.
Do this insteadRely on top to know which positions are in use, rather than on the contents of the array.
Common mistake Popping from an empty stack without the guard: pop must throw an IllegalStateException when top is 0
When tracing an array stack by hand, write down top before every operation and redo the arithmetic each time. Locating the top item as position top-1 avoids the upside-down trap entirely.
Comparing the Two Implementations
| Aspect | Array-based stack | Linked-list stack |
|---|---|---|
| Where the top is | Position top-1, found by arithmetic on the counter | The head node, found by following the top pointer |
| Push | Store at position top, then add 1 to top | Create a node pointing at the old top, then move top to it |
| Pop | Read position top-1, subtract 1 from top, return the value | Save the head's item, move top to the second node, return the item |
| isEmpty | True when top equals 0 | True when top is null |
| Capacity | Fixed unless a dynamic array grows it by doubling | Nodes are added as needed |
| Shared behavior | Same push, pop, and isEmpty operations | Same push, pop, and isEmpty operations |
Both implementations expose the same stack operations through different internal top tracking.
Practice Tracing a Stack
Starting from an empty array stack with top = 0, perform these operations in order: push 4, push 6, push 2, pop, push 9, pop, pop. Predict the value returned by each pop, then state the final top and whether isEmpty returns true or false.
Hints
- After each push, the new item goes to position top, and then top increases by 1.
- Each pop reads position top-1, so the most recent item still in use is removed first.
- After pop returns 2, top is 2, so the next push writes into position 2.
A linked-list stack has top pointing to a node holding 5, which points to a node holding 1. Describe what happens to the top pointer when you push 7 and then pop twice. Name each returned value in order.
Hints
- Push makes the new node's next reference point at the node holding 5.
- Pop returns the head item and moves top to the node beneath it.
Key Takeaways
- A stack is an abstract data type that follows Last-In-First-Out, and array and linked-list implementations expose the same push, pop, and
isEmptyoperations. - In an array-based stack, items occupy positions 0 through top-1, so the top item is at position top-1 and index 0 is the bottom, not the top.
- In a linked-list stack, the top is the head node, and push and pop work at the front of the list.
- A dynamic array checks whether top equals its length before a push, and when full it copies the items into an array of twice the size so the stack can keep growing.
- The resize step changes only the storage, not the order of items or the behavior callers see.