A queue is a basic data structure that follows the first in, first out (FIFO) principle, meaning that elements are processed in the order they arrive. Unlike arrays or linked lists, a queue only allows you to add items at the back and remove them from the front. This ensures that the oldest elements are handled first.
Solving 5 common coding interview problems with queues
As an interviewer at FAANG companies and Educative, I’ve seen how choosing the right data structure can be the difference between an optimal solution and a missed opportunity, especially when time is of the essence. Understanding data structures deeply is crucial for coding interviews, as your choice directly affects time and space complexity.
One essential data structure is the queue, which is indispensable for real-world problems and interview challenges where maintaining order is key. A queue operates on a first-in, first-out (FIFO) principle, where items are processed in the order they arrive—just like a line at a ticket counter.
In this blog, I will cover the following:
How data structures are essential to design optimized algorithms
The fundamental concept and basic operations of queues.
Different variations of queues:
Circular Queue
Priority Queue
Deque
Concurrent Queue
Blocking Queue
Identifying problems that can be solved using queues
5 coding problems & solutions using queues:
Level order traversal of binary tree
Find the maximum in sliding window
Binary tree zigzag level order traversal
Course schedule II
Number of islands
Basic operations of a queue#
Like any other data structure, a queue offers some basic operations for data manipulation. We can insert (enqueue) and remove (dequeue) data to and from a queue. We can also check whether the queue’s current state is empty. In the primary variation of a queue, an item can only be enqueued at the rear, and an item can only be removed from the front. On the other hand, the queue does not allow manipulation of the data from the center.
The following are the basic operations a queue offers:
Enqueue: Adds an element at the queue end.
Dequeue: Removes an element from the queue front and returns it.
Peek: Returns the element from the queue front without removing it.
Is Empty: Check if the queue does not contain any item.
Let’s see the queue implementation, which includes different methods to perform the aforementioned operations.
Different variations of the queue#
The basic concept of a queue is to enqueue new elements at the rear and dequeue existing elements from the front. Several variations of the queue are also designed to address specific needs and optimize performance for different scenarios. Let’s discuss the various types of queues and highlight their unique characteristics.
Circular Queue#
A specialized queue data structure where the last element connects back to the first, forming a circular arrangement. This design optimizes memory usage, especially for fixed-size queues. Unlike a standard queue, which requires shifting elements to reuse empty spaces, a circular queue allows continuous usage of memory locations without overhead. The empty spaces appear when we dequeue an element from the front of the queue. Instead of wasting the space, a circular queue connects its rear back to the front to use that freed-up space from enqueuing new elements. Task Scheduling and Memory allocation are examples of using circular queues in practical applications. Task scheduling in operating systems requires processes to be scheduled. Round Robin is one such algorithm where a circular queue of processes is maintained to assign CPU time. For managing data packets in network data management, a circular queue ensures that the packets are processed in the order they arrive and reuse the space efficiently.
Priority Queue#
A type of queue in which each element has a priority level assigned to it. The priority level is assigned so that the elements with higher priority are removed from the queue before those with lower priority. This is done regardless of their order of arrival. Priority queues are commonly used in data compression algorithms such as Huffman coding. They are also widely used in network routing to find paths using the A* search algorithm.
Deque#
A deque (pronounced “deck”), also known as a double-ended queue, is a variation of the queue that allows elements to be added or removed from both ends. This means we can add or remove an element on the queue’s front or rear end. A deque can be further classified into
Concurrent Queue#
A thread-safe queue designed to simultaneously handle multiple operations from different processes. Concurrent queues are designed to make sure that data keeps synced when multiple threads access it. They are used in event-driven programming to manage incoming events from different sources, such as user input or network messages, and ensure that events are processed synchronously.
Blocking Queue#
A type of queue that waits for itself to become non-empty before retrieving an element and also waits for space to become available before storing an element. They are commonly used in thread pool management. When a task is added to a thread pool, it is also added to a blocking queue. Worker threads continuously retrieve tasks from the blocking queue. If the queue is empty, worker threads wait until a task becomes available.
How to identify problems that can be solved using queues#
Learning to quickly identify queue-appropriate problems is crucial for tech interviews. We can solve a problem using a queue if the problem has one or more of the following indications:
First-in-first-out (FIFO) order of the elements:
If we are solving a problem that requires processing the elements in the sequence of their arrival, the queue would be the best data structure.
Using the queue, we will ensure that we follow the exact requirements of the problem by processing the items that arrive first and putting the new elements at the end of the queue.
The common problems include task scheduling, where we want to execute the tasks in the order they arrive and print jobs to print the documents as they are enqueuing.
Level order traversal:
A queue can solve problems requiring traversing a data structure’s elements level by level.
Using a queue helps manage the nodes at each level. This way, we can ensure we processed all nodes at one level before moving on to the next level.
A breadth-first traversal of the trees and graphs is a common problem that we can solve using level order traversal.
Producer-consumer problem:
When solving a problem where some producers generate data and one or more consumers process that data, a queue can store the data generated by the producer in a buffer for consumers to use.
Queues ensures smooth data flow between the producer and consumer.
The example includes the logging system, where the queue will store the system logs as they arrive before storing them on the disk. Data streaming is another example where a queue helps store a stream’s data before presenting it to the user.
Sliding window problems:
When solving problems that require tracking a subset of elements within a fixed-size window moving through a dataset, we often use queues.
A queue helps manage the elements in the current window, making it easy to update the window as new elements enter and old ones leave.
A typical example of identifying the sliding window pattern is finding the maximum or minimum value in a sliding window using the queue to record the elements within the current window.
Solving 5 queue coding interview questions#
Let’s talk about the coding problems that can be solved using the queue or one of its variations.
1. Level order traversal of binary tree#
Display the nodes’ values of a binary tree using a level order traversal.
Solution#
The optimized solution to this problem is to use a deque to traverse the tree’s nodes level-by-level efficiently.
Create the following two lists and deques:
A deque for traversing the tree level by level
An empty list for nodes at the current level
A list of nodes from all levels
Enqueue the root node into the deque and loop until the deque is empty. For each node, dequeue it, add it to the current level list, and enqueue its children if they exist. After processing all nodes at the current level, join the current-level nodes into a comma-separated string and add it to the result list. Finally, all level strings from the result list are joined into a single string with colons as separators and printed, representing the level order traversal of the binary tree.
The time complexity of this solution is linear, that is,
2. Find the maximum in sliding window#
Given an integer list, find the maximum values in all contiguous subarrays of a specific size.
Solution#
The optimized solution to this problem is using a sliding window and a queue. First, if the input list has only one element, return it immediately since no further processing is needed. Next, use a deque for the first
Then, move through the rest of the input list, repeating the cleanup and append steps for each element. Before adding the current element’s index to the deque during each iteration, remove the first index if it’s no longer within the current window. Finally, return the list that contains the maximum values for each window.
When the window initially moves forward, and if the new element is larger than all other elements in the deque, the pop operation must be performed
3. Binary tree zigzag level order traversal#
Given a binary tree, return its zigzag level order traversal. In this traversal, nodes are traversed from left to right at one level, then from right to left at the next level, and this alternating pattern continues, reversing the direction at each subsequent level.
Solution#
The optimized solution to this problem is to use the deque to track all the nodes at a level. We will use each level’s first-in-first-out (FIFO) and last-in-first-out (LIFO) schemes to perform the traversal in a zigzag level order. We append new elements to the deque’s tail to ensure FIFO ordering. Likewise, we append the new elements to the head of the deque to ensure LIFO ordering.
We will start by adding the tree’s root to a deque for level order traversal and set a reverse flag to manage alternation between FIFO and LIFO schemes. The reverse flag starts as false and toggles at each level. When it is false, pop nodes from the front (left) of the deque and push their children to the back (right), ensuring right-to-left traversal. When it is true, pop nodes from the back (right) and push their children to the front (left), ensuring left-to-right traversal.
This solution’s time complexity is
4. Course schedule #
Consider a scenario with a set of courses labeled sequentially, some of which have prerequisites. The prerequisites are given as pairs, indicating that one course must be completed before the other. The task is to determine a valid order for students to complete all the courses.
Solution#
The optimized solution to this problem is to use a queue and topological sort to find the linear ordering of elements.
The graph is stored using adjacency lists managed through a hash map, where each parent vertex number is a key linked to a list of child vertex numbers. We use another hash map to track the in-degrees of each vertex, indicating the count of incoming edges. Vertices with an in-degree of 0 are considered sources. The graph is built from input data while updating the in-degrees hash map. Sources are added to a queue for processing. For each source in the queue, we add it to the sorted list, retrieve its children, decrease each child’s in-degree by 1, and if a child’s in-degree becomes 0, add it to the source queue. We will repeat this process until the queue is empty.
The time complexity of this solution is
5. Number of islands#
Imagine a scenario with an
Solution#
The optimized solution to this problem is to use a queue and explore the island with the help of Breadth-First-Search (BFS).
To find the number of islands in a grid, initialize a counter to keep track of the number of islands found. Iterate through each cell in the grid and use a queue to explore all parts of an island, starting from the first land cell (the cell with value
This solution’s time complexity is
From data structures to patterns#
Mastering the queue data structure is a critical step toward solving a wide range of coding interview problems, from breadth-first search in trees to task scheduling in operating systems. Queue is often the go-to whenever order and sequence matter. By understanding how and when to use queues, you can tackle problems more quickly in the coding interview and on the job.
That said, mastering individual data structures like queues is not enough to succeed. To truly excel in coding interviews, your best bet is to focus on identifying common patterns that underly all coding problems. Coding patterns will help you identify the optimal solution by recognizing a problem's underlying structure—saving precious time during interviews. While it's essential to keep refining your understanding of data structures, you should start thinking in patterns to truly prepare for the unknown in the interview.
You can learn about 26 coding interview patterns that helps solve hundreds of problems (including the 5 we solved today) with Educative's Grokking Coding Interview Patterns series, check them out below.
You can explore more hands-on interview prep resources at Educative.
Happy interviewing!
Check out the following blogs about data structures:
Frequently Asked Questions
What is a queue, and how does it differ from other data structures?
What is a queue, and how does it differ from other data structures?
What are the basic operations of a queue?
What are the basic operations of a queue?
What is a circular queue, and when is it useful?
What is a circular queue, and when is it useful?
How does a priority queue differ from a standard queue?
How does a priority queue differ from a standard queue?
What problem-solving scenarios are best suited for using a queue?
What problem-solving scenarios are best suited for using a queue?