C# Stack and Queue — push, pop, enqueue, dequeue, and when to use each

Stack<T> and Queue<T> are two fundamental collection types in C# that impose a specific access order. Use them when the order you add items must dictate the order you remove them.

  • Stack — Last In, First Out (LIFO). Think of a stack of plates: you add and remove from the top.
  • Queue — First In, First Out (FIFO). Think of a checkout line: first to arrive is first to leave.

Stack<T> — LIFO

Basic Operations

C# Example Code
var stack = new Stack<int>();

// Push — add to the top
stack.Push(1);
stack.Push(2);
stack.Push(3);

Console.WriteLine($"Count: {stack.Count}"); // Count: 3

// Peek — look at the top without removing
Console.WriteLine($"Top: {stack.Peek()}");  // Top: 3

// Pop — remove and return the top item
int top = stack.Pop();
Console.WriteLine($"Popped: {top}");         // Popped: 3
Console.WriteLine($"Count: {stack.Count}");  // Count: 2

// Iterate — from top to bottom (does not remove items)
foreach (var item in stack)
    Console.Write($"{item} ");
// Output: 2 1

TryPop and TryPeek (safe versions)

C# Example Code
var stack = new Stack<string>();
stack.Push("first");

// Returns false and default if empty — no exception
if (stack.TryPop(out string? value))
    Console.WriteLine($"Popped: {value}");   // Popped: first

bool hasPeek = stack.TryPeek(out string? top);
Console.WriteLine(hasPeek); // False — stack is empty

Stack Example — Undo History

C# Example Code
var history = new Stack<string>();

// User performs actions
history.Push("Typed 'Hello'");
history.Push("Bold applied");
history.Push("Font changed to Arial");

Console.WriteLine("Action log (most recent first):");
foreach (var action in history)
    Console.WriteLine($"  {action}");

// Undo last action
string? undone = history.TryPop(out var last) ? last : null;
Console.WriteLine($"\nUndone: {undone}");  // Undone: Font changed to Arial
Console.WriteLine($"Remaining: {history.Count}"); // Remaining: 2

Stack Example — Balanced Parentheses Checker

C# Example Code
bool IsBalanced(string expression)
{
    var stack = new Stack<char>();

    foreach (char c in expression)
    {
        if (c is '(' or '[' or '{')
        {
            stack.Push(c);
        }
        else if (c is ')' or ']' or '}')
        {
            if (stack.Count == 0) return false;

            char open = stack.Pop();
            if ((c == ')' && open != '(') ||
                (c == ']' && open != '[') ||
                (c == '}' && open != '{'))
                return false;
        }
    }

    return stack.Count == 0;
}

Console.WriteLine(IsBalanced("({[hello]})"));  // True
Console.WriteLine(IsBalanced("({[hello]}"));   // False — missing closing
Console.WriteLine(IsBalanced("({[hello])}"));  // False — wrong order

Queue<T> — FIFO

Basic Operations

C# Example Code
var queue = new Queue<string>();

// Enqueue — add to the back
queue.Enqueue("Alice");
queue.Enqueue("Bob");
queue.Enqueue("Carol");

Console.WriteLine($"Count: {queue.Count}"); // Count: 3

// Peek — look at the front without removing
Console.WriteLine($"Next: {queue.Peek()}"); // Next: Alice

// Dequeue — remove and return the front item
string first = queue.Dequeue();
Console.WriteLine($"Served: {first}");       // Served: Alice
Console.WriteLine($"Count: {queue.Count}");  // Count: 2

// Iterate — front to back (does not remove)
foreach (var item in queue)
    Console.Write($"{item} ");
// Output: Bob Carol

TryDequeue and TryPeek

C# Example Code
var queue = new Queue<int>();
queue.Enqueue(10);

if (queue.TryDequeue(out int value))
    Console.WriteLine($"Dequeued: {value}"); // Dequeued: 10

bool hasNext = queue.TryPeek(out int front);
Console.WriteLine(hasNext); // False — queue is empty

Queue Example — Task Processor

C# Example Code
var taskQueue = new Queue<string>();

// Producer adds tasks
taskQueue.Enqueue("Send email to Alice");
taskQueue.Enqueue("Generate report");
taskQueue.Enqueue("Backup database");

// Consumer processes tasks in order
while (taskQueue.TryDequeue(out string? task))
{
    Console.WriteLine($"Processing: {task}");
}

// Output:
// Processing: Send email to Alice
// Processing: Generate report
// Processing: Backup database

PriorityQueue<TElement, TPriority> (.NET 6+)

PriorityQueue<T, P> dequeues items in order of priority (lowest priority number first).

C# Example Code
var pq = new PriorityQueue<string, int>();

pq.Enqueue("Low priority task",    3);
pq.Enqueue("Critical bug fix",     1);
pq.Enqueue("Feature request",      2);
pq.Enqueue("Another critical fix", 1);

while (pq.TryDequeue(out string? task, out int priority))
    Console.WriteLine($"[P{priority}] {task}");

// [P1] Critical bug fix
// [P1] Another critical fix
// [P2] Feature request
// [P3] Low priority task

Stack vs Queue — Quick Comparison

Stack<T>Queue<T>
OrderLIFO — last in, first outFIFO — first in, first out
AddPush()Enqueue()
RemovePop()Dequeue()
Look without removingPeek()Peek()
Safe operationsTryPop(), TryPeek()TryDequeue(), TryPeek()
Common use casesUndo history, parsing, DFSTask queues, BFS, request buffering
Thread-safe alternativeConcurrentStack<T>ConcurrentQueue<T>

Performance

Both Stack<T> and Queue<T> provide O(1) push/pop/enqueue/dequeue operations. Internal resizing (like List<T>) is O(n) amortized but rare.

C# Example Code
// All four core operations are O(1):
var s = new Stack<int>();
s.Push(1);      // O(1)
s.Pop();        // O(1)

var q = new Queue<int>();
q.Enqueue(1);   // O(1)
q.Dequeue();    // O(1)

Thread-Safe Alternatives

For multi-threaded scenarios, use the concurrent variants from System.Collections.Concurrent.

C# Example Code
using System.Collections.Concurrent;

var safeStack = new ConcurrentStack<int>();
safeStack.Push(1);
safeStack.TryPop(out int _);

var safeQueue = new ConcurrentQueue<string>();
safeQueue.Enqueue("task");
safeQueue.TryDequeue(out string? _);