Why I Ended Up Implementing a Stack From Scratch in Go
Recently, during an interview, I was asked a standard question:
Validate a string of parentheses.
I solved it using stack.
But then the interviewer followed up with something unexpected:
“How would you implement a stack from scratch?”
And yeah… my brain froze.
After that, I decided to sit down, revise the fundamentals, and actually build a stack from scratch in Go.
What Exactly Is a Stack?
A stack is one of the simplest data structures.
It follows a very straightforward rule:
LIFO - Last In, First Out
A good analogy is a stack of plates:
- You place plates on top (Push)
- You remove plates from the top (Pop)
- You never access items from the middle or bottom
The most recently added item is always the first one removed.
Core Operations of a Stack
A stack supports four basic operations:
Push - Add an item to the top
Pop - Remove and return the top item
Peek - Return the top item without removing it
IsEmpty - Check whether the stack contains any elements
These simple operations form the basis of many algorithms.
So here is the implementation.
Stack Implementation in Go (Using Generics)
package main
import "fmt"
func main() {
var st Stack[int]
st.Push(10)
fmt.Println(st.Pop())
fmt.Println(st.Peek())
fmt.Println(st.IsEmpty())
st.Push(71)
fmt.Println(st.IsEmpty())
}
// stack
type Stack[T any] struct {
data []T
}
// Push
func (s *Stack[T]) Push(value T) {
s.data = append(s.data, value)
}
// Pop
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.data) == 0 {
return zero, false
}
value := s.data[len(s.data)-1]
s.data = s.data[:len(s.data)-1]
return value, true
}
// Peek
func (s *Stack[T]) Peek() (T, bool) {
var zero T
if len(s.data) == 0 {
return zero, false
}
return s.data[len(s.data)-1], true
}
// IsEmpty
func (s *Stack[T]) IsEmpty() bool {
return len(s.data) == 0
}
type Stack[T any] struct {
data []T
}
What This Means
-
Stackis a generic type. -
Trepresents any type: int, string, rune, struct, etc. - Internally, values are stored in a slice (
[]T).
Slices naturally fit stack behavior because:
-
append()adds an element to the end (Push) - slicing (
[:len-1]) removes the last element efficiently (Pop)
1. Push - Add an Item to the Top
func (s *Stack[T]) Push(value T) {
s.data = append(s.data, value)
}
Push appends the element to the end of the slice, which represents the top of the stack.
Using a pointer receiver (*Stack[T]) ensures the original stack is modified.
2. Pop - Remove and Return the Top Element
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.data) == 0 {
return zero, false
}
value := s.data[len(s.data)-1]
s.data = s.data[:len(s.data)-1]
return value, true
}
- If the stack is empty, Pop returns
(zero value, false) - Otherwise, it returns the last element and removes it using slicing
zero is the default value of type T (0 for int, "" for string, false for bool, etc.)
3. Peek - View the Top Element Without Removing It
func (s *Stack[T]) Peek() (T, bool) {
var zero T
if len(s.data) == 0 {
return zero, false
}
return s.data[len(s.data)-1], true
}
Peek returns the top element but leaves the stack unchanged.
4. IsEmpty - Check Whether the Stack Is Empty
func (s *Stack[T]) IsEmpty() bool {
return len(s.data) == 0
}
A simple check based on slice length.
Example Usage
var st Stack[int]
st.Push(10)
fmt.Println(st.Pop()) // (10, true)
fmt.Println(st.Peek()) // (0, false)
fmt.Println(st.IsEmpty()) // true
st.Push(71)
fmt.Println(st.IsEmpty()) // false
Summary
This implementation:
- Uses Go generics
- Works for any type
- Is clean, efficient, and easy to understand
- Follows true LIFO behavior
- Demonstrates how a stack is built internally
This is a complete, from-scratch stack implementation written in Go using generics.
Top comments (0)