Stack in C# : Understanding the LIFO Collection in Depth
When you need to handle data where the most recently added item is the first one to be used, the Stack collection in C# is exactly what you need. It works on the Last-In, First-Out (LIFO) principle just like a stack of plates, where you always take the one on top before reaching the ones below.
Whether you’re building an undo/redo feature, handling nested method calls or implementing a depth-first search algorithm, Stack<T> provides a simple and efficient way to manage data using the LIFO approach. Knowing how it works and when to use it can really help you write cleaner, more maintainable code, especially in situations where the order of processing is just as important as the data itself.
public class Stack<T> :
System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IReadOnlyCollection<T>, System.Collections.ICollection
- Stack operates on the last-in-first-out (LIFO) principle.
- You should use Stack where you need to access the information/data in reverse order that it is stored.
- Stack can store duplicate elements.
- Stack is not Thread Safe.
What is Stack<T> in C#?
The Stack<T> class, part of the System.Collections.Generic namespace in .NET, works on the Last-In, First-Out (LIFO) concept. In simple terms, the last element you add to the stack is the first one that comes out just like taking the top item from a stack of books.
Think of it like a stack of plates, you add (or push) new plates on top and when you take one off (or pop), you always remove the top plate first. The Stack<T> collection works the same way, all operations happen at the top of the stack.
Key Characteristics of Stack<T>
- LIFO Order (Last-In, First-Out) : In a stack, the last item you add is always the first one to be removed (using Pop()). This behavior makes it perfect for situations like backtracking, undo/redo features and handling nested or recursive operations.
- Allows Duplicate Elements : You can push the same value onto a Stack<T> more than once. It doesn’t check for duplicates since what really matters is the order of items and how they’re stacked, not whether each value is unique.
- Supports Null Values (Reference Types) : If T is a reference type, you can push null onto the stack just like any other item. This can be useful when you need to represent missing or placeholder values in your collection.
- Internal Dynamic Resizing : The Stack<T> collection stores its elements in an internal array. When that array fills up, it automatically expands (usually by doubling its size), so you don’t have to worry about managing the capacity yourself.
- No Random Index Access : Unlike lists or arrays, Stack<T> does not allow accessing or modifying elements at arbitrary positions. You can only interact with the top element using Push, Peek or Pop operations.
- Enumerator Doesn’t Modify State : You can use a foreach loop to iterate through a Stack<T> and the elements are returned in LIFO order (from the top of the stack downwards). What’s great is that this process is non-destructive meaning the stack remains completely unchanged after enumeration. You’re simply viewing the elements, not altering them.
- Not Thread-Safe by Default : The Stack<T> collection is not inherently synchronized for multi-threaded use. This means that if multiple threads try to push or pop items at the same time, unexpected behavior or data corruption can occur. In such cases, you should either apply your own locking mechanism or use ConcurrentStack<T>, which is designed specifically for safe, concurrent operations.
- Memory Efficiency for LIFO Scenarios : Because of its simple internal array implementation and the absence of complex node structures (as in linked lists), Stack<T> is often more memory-efficient when you only need push/pop behavior rather than random access or heavy insertion/deletion in the middle.
Stack Constructors
1. Stack<T>()
public Stack<T>();
Initializes a new instance of the Stack<T> class that is empty and uses the default initial capacity.
Examplevar stack = new Stack<string>();
Usage : Use this when you need an empty stack and don’t know how many items you’ll push.
2. Stack<T>(int capacity)
public Stack<T>(int capacity);
Initializes a new empty instance of the Stack<T> class with the specified initial capacity (or the default if the provided value is less).
Examplevar stack = new Stack<int>(100);
Usage : Use this when you know an approximate number of items that will be pushed and want to avoid multiple internal array resizes.
3. Stack<T>(IEnumerable<T> collection)
public Stack<T>(IEnumerable<T> collection);
Initializes a new instance of the Stack<T> class that contains elements copied from the specified collection and has sufficient capacity to accommodate those elements.
Examplevar existingList = new List<string> { "A", "B", "C" };
var stack = new Stack<string>(existingList);Usage : Use this when you already have a collection and want to create a stack pre-populated with its items.
Stack Properties
1. Count
public int Count { get; }Retrieves the number of elements currently stored in the Stack<T>. This value reflects how many items are in the stack, not how many it can hold (i.e., capacity).
Examplevar stack = new Stack<string>();
stack.Push("A");
stack.Push("B");
Console.WriteLine(stack.Count); // Output: 2Internal Working of Stack<T>
The Stack<T> class in C# uses a dynamic array internally (_array) to store its elements. Unlike Queue<T>, which manages two pointers (head and tail), Stack<T> only manages a _size counter that represents how many elements are currently stored in the stack.
When you push an element, it’s placed at the end of the array. When you pop, the top element (last pushed) is removed and returned.
If the internal array becomes full, the stack automatically resizes (usually doubles its capacity) to accommodate more elements.
Step-by-Step Visualization Example
Initial Setup: Capacity = 4 (usually default capacity), Size = 0, Array = [_, _, _, _]
| Step | Operation | Size | Capacity | Internal Array (_array) |
Notes |
|---|---|---|---|---|---|
| 1 | Push(10) |
1 | 4 | [10, _, _, _] | Insert 10 at index 0. |
| 2 | Push(20) |
2 | 4 | [10, 20, _, _] | Insert 20 at index 1. |
| 3 | Push(30) |
3 | 4 | [10, 20, 30, _] | Insert 30 at index 2. |
| 4 | Push(40) |
4 | 4 | [10, 20, 30, 40] | Stack is now full. |
| 5 | Push(50) |
5 | 8 (Resized) | [10, 20, 30, 40, 50, _, _, _] | Capacity doubled to 8 as stack exceeded current capacity. |
| 6 | Peek() -> 50 |
5 | 8 | [10, 20, 30, 40, 50, _, _, _] | Returns top element (50) without removing it. |
| 7 | Pop() -> 50 |
4 | 8 | [10, 20, 30, 40, _, _, _, _] | Removes top element (50). Stack size decreases. |
| 8 | Push(60) |
5 | 8 | [10, 20, 30, 40, 60, _, _, _] | Push new top element (60) at index 4. |
| 9 | Pop() -> 60 |
4 | 8 | [10, 20, 30, 40, _, _, _, _] | Removes 60 , top now 40. |
| 10 | Pop() → 40 |
3 | 8 | [10, 20, 30, _, _, _, _, _] | Removes 40, top becomes 30. |
Time and Space Complexity
| Operation | Time Complexity | Space Complexity | Description |
|---|---|---|---|
Push() |
O(1) amortized | O(1) | Adds an item at the top of the stack. Occasional resize may take O(n). |
Pop() |
O(1) | O(1) | Removes and returns the top element from the stack. |
Peek() |
O(1) | O(1) | Returns the top element without removing it. |
Contains() |
O(n) | O(1) | Scans through elements to find a specific match. |
ToArray() |
O(n) | O(n) | Copies all stack elements into a new array (in reversed order). |
TrimExcess() |
O(n) | O(n) | Reduces capacity to match the current count, reallocating the internal array. |
Creating and adding element in Stack<T>
Example 1: Basic Stack<T> Operations
This example shows how to create an empty Stack<T>, push several elements onto it, then use Peek() and Pop() to inspect and remove the topmost elements in LIFO (Last-In, First-Out) order.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var stack = new Stack<string>();
stack.Push("Python");
stack.Push("Java");
stack.Push("C#");
stack.Push("React");
stack.Push("C++");
Console.WriteLine("Peek element: " + stack.Peek());
Console.WriteLine("Pop: " + stack.Pop());
Console.WriteLine("After Pop, Peek element: " + stack.Peek());
Console.WriteLine("\nRemaining elements:");
foreach (var item in stack)
Console.WriteLine(item);
}
}Peek element: C++ Pop: C++ After Pop, Peek element: React Remaining elements: React C# Java Python
Example 2: Initializing Stack<T> from a Collection
Creating a Stack<T> directly from an existing collection. Elements from the source list are pushed onto the stack in their original order, and then enumeration shows the stack contents from top to bottom (i.e., most recent item first).
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var languages = new List<string> { "Go", "Rust", "Kotlin" };
var stack = new Stack<string>(languages);
foreach (var item in stack)
Console.WriteLine(item);
}
}Kotlin Rust Go
Note: While the source collection is ordered Go -> Rust -> Kotlin, the stack pushes those in that order and therefore Kotlin ends up at the top, so enumeration appears Kotlin -> Rust -> Go.
