Implement Queues in TypeScript

Recently, I was working on a TypeScript project where I needed to process user transactions in the exact order they were received. The issue was that there’s no built-in Queue data structure in TypeScript. So we need a proper implementation to handle this common scenario.

In this article, I’ll explain various ways to implement queues in TypeScript. The following examples will cover methods ranging from basic arrays to specialized classes.

What is a Queue in TypeScript?

A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. Think of it like a line at a Starbucks – the first person who joins the line gets served first.

In TypeScript projects, queues are incredibly useful for:

  • Processing tasks in order (like user form submissions)
  • Managing API requests that need to be handled sequentially
  • Implementing breadth-first search algorithms
  • Building event processing systems

Method 1: Using Arrays as Queues in TypeScript

The simplest way to implement a queue in TypeScript is to use the built-in array methods:

// Create a simple queue using an array
let queue: number[] = [];

// Enqueue (add) elements to the queue
queue.push(1);
queue.push(2);
queue.push(3);

console.log(queue); // Output: [1, 2, 3]

// Dequeue (remove) the first element from the queue
const firstItem = queue.shift();
console.log(firstItem); // Output: 1
console.log(queue); // Output: [2, 3]

// Check if queue is empty
const isEmpty = queue.length === 0;
console.log(isEmpty); // Output: false

// Peek at the first element without removing it
const firstInLine = queue[0];
console.log(firstInLine); // Output: 2

Output:

Create queues using array in TypeScript

This approach is straightforward but has performance limitations. The shift() operation has O(n) time complexity because it requires reindexing all remaining elements.

Check out: Check If a Variable is Undefined in TypeScript

Method 2: Creating a Queue Class in TypeScript

For more control and better encapsulation, we can create a dedicated Queue class:

class Queue<T> {
    private items: T[] = [];

    // Add element to the queue
    enqueue(element: T): void {
        this.items.push(element);
    }

    // Remove the first element from the queue
    dequeue(): T | undefined {
        return this.items.shift();
    }

    // View the first element without removing it
    peek(): T | undefined {
        return this.items[0];
    }

    // Check if queue is empty
    isEmpty(): boolean {
        return this.items.length === 0;
    }

    // Get the size of the queue
    size(): number {
        return this.items.length;
    }

    // Clear the queue
    clear(): void {
        this.items = [];
    }
}

// Usage example
const orderQueue = new Queue<string>();
orderQueue.enqueue("Order #1234 - Chicago Deep Dish Pizza");
orderQueue.enqueue("Order #5678 - New York Style Bagel");
orderQueue.enqueue("Order #9012 - California Sushi Roll");

console.log(orderQueue.size()); // Output: 3
console.log(orderQueue.peek()); // Output: "Order #1234 - Chicago Deep Dish Pizza"

const nextOrder = orderQueue.dequeue();
console.log(nextOrder); // Output: "Order #1234 - Chicago Deep Dish Pizza"
console.log(orderQueue.size()); // Output: 2

Output:

Queue in TypeScript

This implementation gives us a more semantic interface to work with queues.

Check out: Check If a Key Exists in an Object

Method 3: Optimized Queue Implementation in TypeScript

If you’re working with large datasets or performance-critical applications, you might want a more efficient queue implementation:

class OptimizedQueue<T> {
    private items: { [key: number]: T } = {};
    private head: number = 0;
    private tail: number = 0;

    enqueue(element: T): void {
        this.items[this.tail] = element;
        console.log(`Enqueued element at position ${this.tail}:`, element);
        this.tail++;
        console.log(`Queue state after enqueue:`, this.items);
    }

    dequeue(): T | undefined {
        if (this.isEmpty()) {
            console.log(`Queue is empty. Cannot dequeue.`);
            return undefined;
        }

        const item = this.items[this.head];
        console.log(`Dequeuing element from position ${this.head}:`, item);
        delete this.items[this.head];
        this.head++;
        console.log(`Queue state after dequeue:`, this.items);
        return item;
    }

    peek(): T | undefined {
        if (this.isEmpty()) {
            console.log(`Queue is empty. Nothing to peek.`);
            return undefined;
        }
        console.log(`Peeking at position ${this.head}:`, this.items[this.head]);
        return this.items[this.head];
    }

    isEmpty(): boolean {
        const empty = this.head === this.tail;
        console.log(`Queue is ${empty ? 'empty' : 'not empty'}`);
        return empty;
    }

    size(): number {
        const size = this.tail - this.head;
        console.log(`Current queue size: ${size}`);
        return size;
    }

    clear(): void {
        console.log(`Clearing the queue.`);
        this.items = {};
        this.head = 0;
        this.tail = 0;
        console.log(`Queue has been cleared.`);
    }
}

Test the Queue:

const queue = new OptimizedQueue<string>();

queue.enqueue("Task 1");
queue.enqueue("Task 2");

queue.peek();         // Should show "Task 1"
queue.dequeue();      // Removes "Task 1"
queue.dequeue();      // Removes "Task 2"
queue.dequeue();      // Queue is empty

queue.enqueue("Task 3");
queue.size();         // Should log size as 1
queue.clear();        // Clears queue
queue.size();         // Should log size as 0

Output:

Dequeue element in TypeScript

This implementation uses an object with numeric keys instead of an array. This approach avoids the costly reindexing operation when dequeuing items, resulting in O(1) time complexity for both enqueue and dequeue operations.

Check out: TypeScript Generic Object Types

Method 4: Using TypeScript Queue Libraries

For production applications, consider using established libraries like:

// Install with: npm install @datastructures-js/queue
import { Queue } from '@datastructures-js/queue';

// Create a new queue
const customerServiceQueue = new Queue<string>();

// Add customers to the queue
customerServiceQueue.enqueue('John from Texas');
customerServiceQueue.enqueue('Sarah from New York');
customerServiceQueue.enqueue('Mike from California');

// Process customers in order
while (customerServiceQueue.size() > 0) {
    const customer = customerServiceQueue.dequeue();
    console.log(`Now serving: ${customer}`);
}

Output:

Implement Queue in TypeScript

External libraries typically offer optimized implementations with additional features that might be useful for complex applications.

Practical Example: Task Processing Queue

Here’s a real-world example of using a queue to process user tasks in order:

interface Task {
    id: string;
    description: string;
    processingTime: number;
}

class TaskProcessor {
    private taskQueue = new Queue<Task>();
    private isProcessing = false;

    addTask(task: Task): void {
        this.taskQueue.enqueue(task);

        if (!this.isProcessing) {
            this.processNextTask();
        }
    }

    private async processNextTask(): Promise<void> {
        if (this.taskQueue.isEmpty()) {
            this.isProcessing = false;
            return;
        }

        this.isProcessing = true;
        const task = this.taskQueue.dequeue()!;

        console.log(`Starting task: ${task.id} - ${task.description}`);

        // Simulate task processing
        await new Promise(resolve => setTimeout(resolve, task.processingTime));

        console.log(`Completed task: ${task.id}`);

        // Process next task
        this.processNextTask();
    }
}

// Usage
const processor = new TaskProcessor();

processor.addTask({
    id: 'task-1',
    description: 'Generate monthly report for New York office',
    processingTime: 2000
});

processor.addTask({
    id: 'task-2',
    description: 'Update inventory for Chicago warehouse',
    processingTime: 1500
});

processor.addTask({
    id: 'task-3',
    description: 'Process payroll for California team',
    processingTime: 3000
});

Output:

Task processing queue in TypeScript

This implementation ensures tasks are processed in the exact order they were submitted, making it perfect for systems where order matters.

Check out:  Set Default Values in TypeScript Interfaces

Circular Queue Implementation

For memory-efficient applications like buffers, circular queues are helpful:

class CircularQueue<T> {
    private capacity: number;
    private items: (T | undefined)[];
    private head: number = 0;
    private tail: number = 0;
    private size: number = 0;

    constructor(capacity: number) {
        this.capacity = Math.max(1, capacity);
        this.items = new Array(this.capacity);
    }

    enqueue(element: T): boolean {
        if (this.isFull()) {
            return false;
        }

        this.items[this.tail] = element;
        this.tail = (this.tail + 1) % this.capacity;
        this.size++;
        return true;
    }

    dequeue(): T | undefined {
        if (this.isEmpty()) {
            return undefined;
        }

        const item = this.items[this.head];
        this.items[this.head] = undefined;
        this.head = (this.head + 1) % this.capacity;
        this.size--;
        return item;
    }

    peek(): T | undefined {
        if (this.isEmpty()) {
            return undefined;
        }
        return this.items[this.head];
    }

    isEmpty(): boolean {
        return this.size === 0;
    }

    isFull(): boolean {
        return this.size === this.capacity;
    }

    getSize(): number {
        return this.size;
    }

    clear(): void {
        this.items = new Array(this.capacity);
        this.head = 0;
        this.tail = 0;
        this.size = 0;
    }
}

Circular queues are excellent for fixed-size buffers, such as handling the most recent user actions or implementing a playlist rotation system.

Conclusion

In this TypeScript tutorial, we learned how to implement queues in TypeScript using different approaches from simple arrays to custom classes like Queue, OptimizedQueue, and CircularQueue. We also learned how to use external libraries for production-ready solutions. These methods help handle tasks in the exact order they arrive, making queues ideal for task processing, API management, and real-time systems.

I hope you found this article helpful in understanding queue implementation in TypeScript. In the above solutions, we have discussed fundamental data structures that can elegantly solve many common programming problems. Whether you choose the simple array approach, create a custom class, or use a library depends on your specific requirements and performance needs.

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.