Code Templates
Standardize your approach to stack-based problems with these production-grade templates. For a deep dive into the theory, check out the Stack Concept Guide.
The Main Template: Standard Stack
While most languages provide a built-in stack (like Python’s list or Java’s Stack), implementing one from scratch is a common interview requirement to demonstrate your understanding of memory and pointers.
class Stack {
constructor() {
this.items = [];
}
// Add element to the top
push(element) {
this.items.push(element);
}
// Remove and return the top element
pop() {
if (this.isEmpty()) return null;
return this.items.pop();
}
// View the top element
peek() {
if (this.isEmpty()) return null;
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
}class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
if self.is_empty():
return None
return self.items.pop()
def peek(self):
if self.is_empty():
return None
return self.items[-1]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)import java.util.ArrayList;
public class Stack<T> {
private ArrayList<T> items = new ArrayList<>();
public void push(T item) {
items.add(item);
}
public T pop() {
if (isEmpty()) return null;
return items.remove(items.size() - 1);
}
public T peek() {
if (isEmpty()) return null;
return items.get(items.size() - 1);
}
public boolean isEmpty() {
return items.isEmpty();
}
public int size() {
return items.size();
}
}#include <vector>
#include <optional>
template <typename T>
class Stack {
private:
std::vector<T> items;
public:
void push(const T& item) {
items.push_back(item);
}
std::optional<T> pop() {
if (isEmpty()) return std::nullopt;
T top = items.back();
items.pop_back();
return top;
}
std::optional<T> peek() const {
if (isEmpty()) return std::nullopt;
return items.back();
}
bool isEmpty() const {
return items.empty();
}
size_t size() const {
return items.size();
}
};type Stack []interface{}
func (s *Stack) Push(v interface{}) {
*s = append(*s, v)
}
func (s *Stack) Pop() interface{} {
if s.IsEmpty() {
return nil
}
res := (*s)[len(*s)-1]
*s = (*s)[:len(*s)-1]
return res
}
func (s *Stack) Peek() interface{} {
if s.IsEmpty() {
return nil
}
return (*s)[len(*s)-1]
}
func (s *Stack) IsEmpty() bool {
return len(*s) == 0
}class Stack
def initialize
@items = []
end
def push(item)
@items.push(item)
end
def pop
@items.pop
end
def peek
@items.last
end
def empty?
@items.empty?
end
def size
@items.length
end
endLogic Breakdown
The fundamental lifecycle of a stack operation can be visualized as a LIFO state machine:
graph LR
A[Start] --> B{Operation?}
B -->|Push| C[Add to Top]
B -->|Pop| D{Is Empty?}
D -->|Yes| E[Error/Null]
D -->|No| F[Remove Top]
C --> G[New State]
F --> G
Key Components
- Pointer/Index: In array-based stacks, the “top” is implicitly the last index. In linked lists, it is the head node.
- Underflow Protection: Always check
isEmpty()before apoporpeekto prevent runtime crashes. - Expansion: Most modern languages handle dynamic array resizing automatically (amortized), so we rarely need to worry about “Stack Overflow” in manual implementations unless memory is strictly capped.O(1)
Variation 1: Monotonic Stack (Next Greater Element)
The Monotonic Stack is the most frequent application of stacks in advanced interviews. It maintains elements in a specific order (increasing or decreasing) to solve range-based problems in linear time.
function nextGreaterElement(nums) {
const stack = [];
const res = new Array(nums.length).fill(-1);
for (let i = 0; i < nums.length; i++) {
// While current is greater than the element at the index on top of stack
while (stack.length > 0 && nums[i] > nums[stack[stack.length - 1]]) {
const index = stack.pop();
res[index] = nums[i];
}
stack.push(i);
}
return res;
}def next_greater_element(nums):
stack = []
res = [-1] * len(nums)
for i in range(len(nums)):
# Maintain a decreasing stack
while stack and nums[i] > nums[stack[-1]]:
index = stack.pop()
res[index] = nums[i]
stack.append(i)
return resimport java.util.Stack;
import java.util.Arrays;
public int[] nextGreaterElement(int[] nums) {
int[] res = new int[nums.length];
Arrays.fill(res, -1);
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < nums.length; i++) {
while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
res[stack.pop()] = nums[i];
}
stack.push(i);
}
return res;
}#include <vector>
#include <stack>
std::vector<int> nextGreaterElement(std::vector<int>& nums) {
std::vector<int> res(nums.size(), -1);
std::stack<int> s;
for (int i = 0; i < nums.size(); ++i) {
while (!s.empty() && nums[i] > nums[s.top()]) {
res[s.top()] = nums[i];
s.pop();
}
s.push(i);
}
return res;
}func nextGreaterElement(nums []int) []int {
res := make([]int, len(nums))
for i := range res {
res[i] = -1
}
stack := []int{} // stores indices
for i, num := range nums {
for len(stack) > 0 && num > nums[stack[len(stack)-1]] {
idx := stack[len(stack)-1]
stack = stack[:len(stack)-1]
res[idx] = num
}
stack = append(stack, i)
}
return res
}def next_greater_element(nums)
res = Array.new(nums.length, -1)
stack = [] # stores indices
nums.each_with_index do |num, i|
while !stack.empty? && num > nums[stack.last]
idx = stack.pop
res[idx] = num
end
stack.push(i)
end
res
endVariation 2: Min Stack (O(1) Min Access)
This template allows you to retrieve the minimum element in the stack in constant time by using an auxiliary stack.
class MinStack {
constructor() {
this.stack = [];
this.minStack = [];
}
push(val) {
this.stack.push(val);
// Push to minStack if empty or val is new minimum
if (this.minStack.length === 0 || val <= this.minStack[this.minStack.length - 1]) {
this.minStack.push(val);
}
}
pop() {
const val = this.stack.pop();
if (val === this.minStack[this.minStack.length - 1]) {
this.minStack.pop();
}
return val;
}
getMin() {
return this.minStack[this.minStack.length - 1];
}
}class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, val: int):
self.stack.append(val)
if not self.min_stack or val <= self.min_stack[-1]:
self.min_stack.append(val)
def pop(self):
if self.stack.pop() == self.min_stack[-1]:
self.min_stack.pop()
def get_min(self) -> int:
return self.min_stack[-1]import java.util.Stack;
class MinStack {
private Stack<Integer> stack = new Stack<>();
private Stack<Integer> minStack = new Stack<>();
public void push(int val) {
stack.push(val);
if (minStack.isEmpty() || val <= minStack.peek()) {
minStack.push(val);
}
}
public void pop() {
if (stack.pop().equals(minStack.peek())) {
minStack.pop();
}
}
public int getMin() {
return minStack.peek();
}
}#include <stack>
class MinStack {
private:
std::stack<int> s;
std::stack<int> min_s;
public:
void push(int val) {
s.push(val);
if (min_s.empty() || val <= min_s.top()) {
min_s.push(val);
}
}
void pop() {
if (s.top() == min_s.top()) {
min_s.pop();
}
s.pop();
}
int getMin() {
return min_s.top();
}
};type MinStack struct {
stack []int
minStack []int
}
func Constructor() MinStack {
return MinStack{}
}
func (s *MinStack) Push(val int) {
s.stack = append(s.stack, val)
if len(s.minStack) == 0 || val <= s.minStack[len(s.minStack)-1] {
s.minStack = append(s.minStack, val)
}
}
func (s *MinStack) Pop() {
val := s.stack[len(s.stack)-1]
s.stack = s.stack[:len(s.stack)-1]
if val == s.minStack[len(s.minStack)-1] {
s.minStack = s.minStack[:len(s.minStack)-1]
}
}
func (s *MinStack) GetMin() int {
return s.minStack[len(s.minStack)-1]
}class MinStack
def initialize
@stack = []
@min_stack = []
end
def push(val)
@stack.push(val)
if @min_stack.empty? || val <= @min_stack.last
@min_stack.push(val)
end
end
def pop
val = @stack.pop
@min_stack.pop if val == @min_stack.last
end
def get_min
@min_stack.last
end
endPractice
Now that you have the blueprints, apply them to real-world scenarios in the Practice Problems section.