Stacks and Queues in C#: Concepts, Code, and Real-World Use Cases
Stacks and queues are two of the most commonly used linear data structures. They form the basis of many algorithms and are essential in solving real-world programming problems. In this post, we’ll walk through what they are, how they work, and how to implement them in C#.
Introduction: LIFO vs FIFO
Before we get into code, it’s important to understand the behavior of these structures:
- Stack: Last In, First Out (LIFO) — like a stack of plates. The last plate added is the first one to be removed.
- Queue: First In, First Out (FIFO) — like a line at a movie theater. The first person in is the first person out.
Stack in C#
Built-In Stack
C# provides a generic stack class in the System.Collections.Generic namespace:
Stack<string> stack = new Stack<string>();
stack.Push("A");
stack.Push("B");
stack.Push("C");
Console.WriteLine(stack.Pop()); // Output: C
Console.WriteLine(stack.Peek()); // Output: B
Custom Stack Implementation
Here’s how to implement a stack using a linked list:
public class Node
{
public int Data;
public Node Next;
public Node(int data) => Data = data;
}
public class CustomStack
{
private Node top;
public void Push(int data)
{
Node newNode = new Node(data);
newNode.Next = top;
top = newNode;
}
public int Pop()
{
if (top == null) throw new InvalidOperationException("Stack is empty");
int data = top.Data;
top = top.Next;
return data;
}
public int Peek() => top?.Data ?? throw new InvalidOperationException("Stack is empty");
}
Real-World Use Cases
- Undo functionality in text editors
- Reversing strings
- Parsing expressions (e.g., balanced parentheses)
- Call stack in recursion
Queue in C#
Built-In Queue
Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");
Console.WriteLine(queue.Dequeue()); // Output: A
Console.WriteLine(queue.Peek()); // Output: B
Custom Queue Implementation
public class QNode
{
public int Data;
public QNode Next;
public QNode(int data) => Data = data;
}
public class CustomQueue
{
private QNode front;
private QNode rear;
public void Enqueue(int data)
{
QNode newNode = new QNode(data);
if (rear == null)
front = rear = newNode;
else
{
rear.Next = newNode;
rear = newNode;
}
}
public int Dequeue()
{
if (front == null) throw new InvalidOperationException("Queue is empty");
int data = front.Data;
front = front.Next;
if (front == null) rear = null;
return data;
}
public int Peek() => front?.Data ?? throw new InvalidOperationException("Queue is empty");
}
Real-World Use Cases
- Task scheduling
- Printer job queues
- Order processing
- Breadth-first search (BFS) in graphs
Bonus: Circular Queue and Priority Queue
Circular Queue
Circular queues are useful when working with fixed-size buffers or scheduling in a circular manner.
Priority Queue
C# 6+ supports PriorityQueue<TElement, TPriority>
in .NET. Used in shortest path algorithms, job scheduling, etc.
Practice Problems
- Implement a function to reverse a string using a stack
- Check for balanced parentheses using a stack
- Simulate a bank queue system
- Reverse the first k elements of a queue
- Design a browser forward/backward navigation using two stacks