LogIn
I don't have account.

Queue in C# ~ FIFO Collection

DevSniper

154 Views

When you need to process items in the exact order they were added, similar to how people wait in line the Queue<T> collection in C# is the ideal choice. It works on the First-In, First-Out (FIFO) principle, where the first element entered is always the first to leave.

This makes Queue<T> especially useful for task scheduling, message processing, printing jobs or asynchronous task management, where maintaining the order of operations is critical .Anytime you need things to happen in the same order they were added, a queue is perfect. It keeps everything organized and makes it easy to manage data that needs to flow in sequence.

What is Queue<T> in C#?

The Queue<T> class in C# is a generic collection that stores elements in the exact order they’re added, following the First-In, First-Out (FIFO) rule. In simple terms, the first item you add is the first one that comes out , just like people waiting in a line.

Behind the scenes, Queue<T> keeps track of elements in the order they arrive, making sure each one is processed in a fair and predictable way. It’s perfect for situations where the order really matters , such as scheduling jobs, managing background tasks or handling requests in a queue-based system.

Namespace :- System.Collections.Generic
Assembly :- System.Collections.dll
Signature :-
public class Queue<T> : 
    System.Collections.Generic.IEnumerable<T>,
    System.Collections.Generic.IReadOnlyCollection<T>,
    System.Collections.ICollection

Key Characteristics of Queue<T>

  • FIFO Order (First-In, First-Out) : In a queue, items are handled in the same order they were added. The first one that goes in, is always the first one to come out. This makes it easy to manage things that need to be processed in order like print jobs, background tasks or any real-world queue system.
  • Allows Duplicate Elements : A Queue<T> doesn’t restrict duplicate values. You can add the same item more than once because it only cares about the order in which elements are added, not whether they’re unique.
  • Supports Null Values (for Reference Types) : When you’re using reference types, Queue<T> lets you add null values as well. This can be useful when you want to represent missing data or mark something as “pending” without removing it from the queue.
  • Automatically Resizes When Needed : Under the hood, Queue<T> uses a circular array to store its elements. When the existing capacity runs out, it automatically grows (usually doubling in size) so you don’t have to worry about managing capacity yourself. This keeps performance smooth and consistent as the queue grows.
  • Indexed Internally, But Not Randomly Accessible : Even though Queue<T> stores its elements in an internal array, you can’t access them directly using an index (like queue[0]). Instead, you can look at the first element with Peek() or remove it using Dequeue() method.
  • Efficient Operations : Both Enqueue() and Dequeue() run in constant time O(1) on average so they stay fast no matter how many elements you add. This makes Queue<T> a reliable choice when performance and order both matter.
  • Not Thread-Safe : Queue<T> isn’t thread-safe, which means it shouldn’t be used by multiple threads at the same time without protection. If you’re working in a multi-threaded environment, you can handle this by using locks or by switching to ConcurrentQueue<T> from System.Collections.Concurrent, which is designed for safe concurrent access.
  • Predictable Enumeration : When you loop through a Queue<T>, the elements are returned in the same order they were added from the oldest to the newest. This means the FIFO order is always preserved while iterating through the collection.

Internal Working of Queue<T>

The Queue<T> class in C# uses a circular array to store its elements. It keeps track of two positions the head for removing items and the tail for adding new ones. Instead of shifting elements each time, it simply adjusts these positions in a circular way, which helps the queue run efficiently.

Step-by-Step Visualization Example

Let’s visualize how Queue<int> manages its internal storage using a circular array.

Initial Setup : Capacity = 5, Head = 0, Tail = 0, Size = 0, Array = [_, _, _, _, _] (empty slots).

Step Operation Head Tail Size Capacity Internal Array (_array) Notes
1 Enqueue(10) 0 1 1 5 [10, _, _, _, _] Insert 10 at index 0. Tail moves to 1.
2 Enqueue(20) 0 2 2 5 [10, 20, _, _, _] Insert 20 at index 1.
3 Enqueue(30) 0 3 3 5 [10, 20, 30, _, _] Insert 30 at index 2.
4 Enqueue(40) 0 4 4 5 [10, 20, 30, 40, _] Insert 40 at index 3.
5 Enqueue(50) 0 0 5 5 [10, 20, 30, 40, 50] Insert 50 at index 4. Tail wraps to 0 (circular).
6 Dequeue() ->10 1 0 4 5 [_, 20, 30, 40, 50] Head moves to 1. 10 removed from the queue.
7 Enqueue(60) 1 1 5 5 [60, 20, 30, 40, 50] Tail wraps to index 0 and inserts 60. Queue full again.
8 Enqueue(70) 0 6 6 10 (resized) [20, 30, 40, 50, 60, 70, _, _, _, _] Resize triggered! Capacity doubled. Elements re-copied in order from head to tail. Head = 0, Tail = 6.
9 Dequeue() -> 20 1 6 5 10 [_, 30, 40, 50, 60, 70, _, _, _, _] Head moves to 1 after removing 20.
10 Enqueue(80) 1 7 6 10 [_, 30, 40, 50, 60, 70, 80, _, _, _] Insert 80 at index 6.
11 Enqueue(90) 1 8 7 10 [_, 30, 40, 50, 60, 70, 80, 90, _, _] Insert 90 at index 7.
12 Enqueue(100) 1 9 8 10 [_, 30, 40, 50, 60, 70, 80, 90, 100, _] Insert 100 at index 8. Queue growing smoothly post-resize.

Time and Space Complexity

Operation Time Complexity Space Complexity Description
Enqueue() O(1) (amortized) O(1) Adds an element at the tail. The only time more space is used is during internal resizing, but that cost is amortized over time.
Dequeue() O(1) O(1) Removes the element at the head efficiently without shifting all elements.
Peek() O(1) O(1) Returns the front element without removing it constant time and space.
Contains() O(n) O(1) Scans through the queue elements sequentially to check if a value exists.
TrimExcess() O(n) O(n) Resizes the internal array to exactly match the number of elements. Requires extra space during the copy operation.
ToArray() O(n) O(n) Creates a new array and copies all queue elements into it. Space proportional to the queue size.

Constructors of Queue<T>

1. Queue<T>()

public Queue<T>();

Initializes a new instance of Queue<T> which is empty, using the default internal capacity. At creation, the queue has no elements and uses whatever default internal array size the implementation provides.

Example
Copy
var queue = new Queue<string>();

Usage : Use this when you want a simple, empty queue and don’t need to pre-allocate capacity.

2. Queue<T>(int capacity)

public Queue<T>(int capacity);

Initializes a new instance of Queue<T> that is empty but with a specified initial capacity. This means the internal storage is pre-allocated to hold at least the number of elements you pass, avoiding early resizing.

Example
Copy
var queue = new Queue<int>(100);

Usage : Use this when you know approximately how many items will be enqueued and you want to optimize for performance by avoiding early resizing.

3. Queue<T>(IEnumerable<T> collection)

public Queue (System.Collections.Generic.IEnumerable<T> collection);

Initializes a new Queue<T> that contains elements copied from the specified collection. The queue’s capacity is set so it can hold all those elements.

Example
Copy
var initialList = new List<string> { "A", "B", "C" };
var queue = new Queue<string>(initialList);

Usage : Use this when you already have a collection of items and want to start with a queue that includes them in the same FIFO order.

Queue<T> Properties

1. Count

int Count { get; }

This property returns the total number of elements currently in the queue.

Example
Copy
var queue = new Queue<string>();
Console.WriteLine(queue.Count);  // Output: 0

It’s a simple way to check how many items are waiting in the queue at any given time.

Creating and adding element in Queue<T>

Example 1: Basic Usage of Queue<T>

Demonstrates how to create an empty queue, enqueue several elements, then use Peek() and Dequeue() to inspect and remove items in FIFO order.

Copy
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var queue = new Queue<string>();
        // Enqueue elements
        queue.Enqueue("Python");
        queue.Enqueue("Java");
        queue.Enqueue("C#");
        queue.Enqueue("React");
        queue.Enqueue("C++");
        Console.WriteLine("Peek element: " + queue.Peek());
        Console.WriteLine("Dequeue: " + queue.Dequeue());
        Console.WriteLine("After Dequeue, Peek: " + queue.Peek());
        Console.WriteLine("\nRemaining elements:");
        foreach (var item in queue)
            Console.WriteLine(item);
    }
}
Peek element: Python
Dequeue: Python
After Dequeue, Peek: Java
Remaining elements:
Java
C#
React
C++

Example 2: Initializing Queue from a Collection

how you can create a Queue<T> directly from another collection, preserving the initial order of its elements.

Copy
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var languages = new List<string> { "Go", "Rust", "Kotlin" };
        var queue = new Queue<string>(languages);
        foreach (var item in queue)
            Console.WriteLine(item);
    }
}
Go
Rust
Kotlin

Conclusion

The Queue<T> collection in C# is a simple yet powerful way to handle data in the exact order it arrives, following the First-In, First-Out (FIFO) approach. Whenever the order of processing matters like in task scheduling, message handling or stream processing a queue ensures that everything is managed fairly and predictably.

If you’re building a job scheduler, a message queue or even a real-time data pipeline, understanding how Queue<T> works can make a big difference. Its operations, Enqueue() and Dequeue(), are fast typically running in constant time so performance remains solid even as your workload increases. And because it’s built on a circular array internally, memory use stays efficient and easy to reason about.

In short, Queue<T> gives you a clean and reliable way to process data in order without extra complexity. Once you get comfortable with it, you’ll find it’s one of the most practical tools for keeping workflows smooth, predictable and well-organized.