Slice As Stack And Queue
Fast recall
Quick revision
Core answer
Whether you understand Slice As Stack And Queue beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
The stack, executed:
stack := []int{}
stack = append(stack, 1) // push
stack = append(stack, 2)
top := stack[len(stack)-1] // peek: 2
stack = stack[:len(stack)-1] // pop: 2, stack=[1]The queue — naive (the trap):
queue := []int{}
queue = append(queue, 1)
queue = append(queue, 2)
front := queue[0] // peek: 1
queue = queue[1:] // pop — PROBLEMThe naive pop's problem: queue[1:] copies the remaining elements (or the old array stays alive until GC — either O(n) per pop or a memory leak pattern). For 1M elements, each pop moves 1M-1.
The proper queue — head index:
type Queue struct {
items []int
head int
}
func (q *Queue) Push(v int) { q.items = append(q.items, v) }
func (q *Queue) Pop() int {
v := q.items[q.head]
q.head++
if q.head > len(q.items)/2 { // compact occasionally
q.items = q.items[q.head:]
q.head = 0
}
return v
}The head-index pattern: pop increments head (O(1)), and the compaction (a copy) happens every half — amortized O(1). The idiomatic Go queue.
The list alternative: container/list (doubly-linked) or a channel (bounded, blocking) — the right tool when the slice's copy-on-compaction matters.
Slice As Stack And Queue
Direct Answer
Stack: append (push) and slice-to-last (pop) — LIFO. Queue: append (push) and slice-from-first (pop) — FIFO, but the naive front-pop copies (or leaks memory) — the proper Go queue uses a head index or a ring buffer.
Deep Dive & Architecture
The stack's slice mechanics: append amortizes (the grow-by-double — the slices topic); the pop is a header update (len decreases — no copy). Both ends O(1) amortized — the slice IS a good stack. The memory subtlety: the underlying array keeps capacity — a stack that grows huge then empties holds the array until GC sees the reference go. For a long-running server's reusable stack, pre-allocate and manage the length yourself.
The queue's amortized analysis: the head-index compact pattern does a copy of size n/2 every n/2 pops — each pop's average cost is (n/2 copy)/(n/2 pops) = O(1). The alternative trigger — 'when head equals len' (fully drained) — avoids the copy entirely but requires the Push to reset — the two forms have the same amortized bound.
The channel-as-queue (the Go-specific option): make(chan int, bufferSize) — a bounded, thread-safe queue with blocking semantics. The right answer when the queue is between goroutines (the producer/consumer pattern) — the channel IS the concurrency-safe queue. For single-goroutine use, the slice is faster; the channel adds synchronization you don't need.
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use Slice As Stack And Queue in a real project?
- How would you compare Slice As Stack And Queue with an alternative?
- What is a common mistake when using Slice As Stack And Queue?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.