.NET Academy

Queues and stacks

Process items in arrival order with Queue<T>, or most-recent-first with Stack<T>.

10 min read

You will learn

  • Explain FIFO and LIFO ordering
  • Use Enqueue/Dequeue and Push/Pop/Peek
  • Recognize problems that call for a stack or a queue
  • Pick the right collection for a job

Order matters

Lists let you access any item at any time. Some problems are about which item comes next:

  • Customers served in the order they arrived → queue (first in, first out — FIFO).
  • Undoing the most recent action first → stack (last in, first out — LIFO).

Using the right collection makes this ordering automatic and the code self-explanatory.

Queue<T>: first in, first out

var line = new Queue<string>();
line.Enqueue("Ada");      // joins the back of the line
line.Enqueue("Bob");
line.Enqueue("Cy");
 
Console.WriteLine(line.Peek());     // Ada — look at the front without removing
Console.WriteLine(line.Dequeue());  // Ada — removes from the front
Console.WriteLine(line.Dequeue());  // Bob
Console.WriteLine(line.Count);      // 1
  Enqueue ─▶ [ Cy | Bob | Ada ] ─▶ Dequeue
              back          front

Queues are everywhere in backend systems: jobs waiting to be processed, messages between services, requests waiting for a worker. You'll meet message queues in Backend Fundamentals.

Stack<T>: last in, first out

var history = new Stack<string>();
history.Push("home");       // goes on top
history.Push("products");
history.Push("cart");
 
Console.WriteLine(history.Pop());   // cart — the most recent
Console.WriteLine(history.Peek());  // products — now on top
        Push ─▶ ┌──────────┐ ─▶ Pop
                │  cart    │  top
                │ products │
                │  home    │
                └──────────┘

Natural uses: undo/redo, the browser's back button, matching brackets, evaluating expressions — and the call stack itself, which tracks which method called which (it's what you read in a stack trace).

Watch out

Dequeue() and Pop() throw InvalidOperationException on an empty collection. Check Count first, or use TryDequeue(out var item) / TryPop(out var item).

Example: undo

var text = new List<string>();
var undo = new Stack<string>();
 
void Type(string word) { text.Add(word); undo.Push(word); }
void Undo() { if (undo.TryPop(out _)) text.RemoveAt(text.Count - 1); }
 
Type("Hello"); Type("wrold"); Undo(); Type("world");
Console.WriteLine(string.Join(" ", text));   // Hello world

Which collection should I use?

You need to…Use
Keep an ordered list, access by index, add/remove anywhereList<T>
Look up values by a keyDictionary<TKey, TValue>
Track unique items / fast "contains"HashSet<T>
Process in arrival orderQueue<T>
Process most recent firstStack<T>
Always take the smallest/most urgent itemPriorityQueue<TElement, TPriority>
A fixed number of itemsT[]

Try it

Program.cs
Loading editor…

C# runtime not loaded

PriorityQueue always dequeues the item with the lowest priority value first.

Key takeaways

  • Queue<T> is FIFO: Enqueue at the back, Dequeue from the front.
  • Stack<T> is LIFO: Push and Pop at the top.
  • Peek looks without removing; TryDequeue / TryPop avoid exceptions on empty collections.
  • Choose collections by the access pattern you need.

Exercise

IsBalanced checks that every bracket — (), [] and {} — is closed in the right order (use a stack). LastToLeave plays "hot potato" with a queue: each round, move `passes` people from the front to the back, then the person at the front leaves. Return the last person remaining.

Program.cs
Loading editor…

C# runtime not loaded

Hint 1

IsBalanced: push every opening bracket. For a closing bracket, the stack must not be empty and Pop() must return the matching opener. At the end, the stack must be empty.

Hint 2

LastToLeave: var queue = new Queue<string>(people); while (queue.Count > 1) { repeat passes times: queue.Enqueue(queue.Dequeue()); then queue.Dequeue(); }

Show solution
Console.WriteLine(IsBalanced("([]{})"));
Console.WriteLine(LastToLeave(new[] { "Ada", "Bob", "Cy", "Dee" }, 1));

bool IsBalanced(string text)
{
    var open = new Stack<char>();
    foreach (char c in text)
    {
        if (c is '(' or '[' or '{')
        {
            open.Push(c);
        }
        else if (c is ')' or ']' or '}')
        {
            if (open.Count == 0) return false;
            char expected = c switch { ')' => '(', ']' => '[', _ => '{' };
            if (open.Pop() != expected) return false;
        }
    }
    return open.Count == 0;
}

string LastToLeave(string[] people, int passes)
{
    var queue = new Queue<string>(people);
    while (queue.Count > 1)
    {
        for (int i = 0; i < passes; i++)
        {
            queue.Enqueue(queue.Dequeue());
        }
        queue.Dequeue();
    }
    return queue.Dequeue();
}

Quiz

4 questions · 3 correct to pass
  1. 1.A Queue<int> receives Enqueue(1), Enqueue(2), Enqueue(3). What does Dequeue() return?

  2. 2.A Stack<int> receives Push(1), Push(2), Push(3). What does Pop() return?

  3. 3.What does Peek() do?

  4. 4.Which is a natural use for a stack?

To complete this lesson

  • ○ Solve the exercise
  • ○ Pass the quiz (3/4 correct)