Consider a simple example of a user creating an application which has the following steps:
User sign’s up
Insert User in DB
Send a Welcome Email to the User
Create Token
Return Response
Now this process is synchronous, meaning each step gets executed one after the other.
This is a perfectly working system for a single user. What if the number of users are now x1,00,000. Let us look at the services which are used here:
Authentication APIs: These APIs are the ones which are built by the user and does not depend on any external dependency.
Database: The database can be hosted by the user on their own server.
Email Service: The email service will be a third party service. This is an external dependency.
Now, what if the email service goes down ? This would result in the signup process to go down as it is dependent on the email service. When 1,00,000 users would be constantly signing up on the service, this would result in the email service thinking that the “Users are SPAMMING” which would result in rate limiting. This is a typical problem which comes with a Synchronous Architecture.
Asynchronous system design involves components or services interacting in a manner where the sender does not wait (or block) for the receiver’s response to continue processing. When designing an asynchronous architecture, you must be able do differentiate between which of the services can be synchronous and which can be asynchronous. In our example, the process remains the same till the signup.
When we come to the task of send mail, we add the sendWelcomeEmail on to the Queue. Remember, we are not sending the mail, just adding the task in the queue. Let us now get into the depth of understanding what is a Message Queue.
The Message Queue is a queue just like the queue we stand on when purchasing tickets for a movie where the messages are kept one after the other. It consists of 2 things: Producer & Consumer. Producer is the one who produces messages into the queue and Consumer is the one which consumes the messages produced by the producer.
Now what can be done here is that, we can have our server on post the queue where the Consumer task is to take the message, process it and then send the email. Since the email service (say we are using Amazon SQS) has a rate limiting of 30 emails / min, we can put the same condition on the server. This will now help the server to be available and the emails to be sent one after the other without any additional load on the server. We can even add a condition saying that once 60 minutes are crossed, server can take a break for a minute and then continue with the next set of emails.
This is called Asynchronous Processing.
Till now we completed with something known as (HLD) High Level Design. It is a high level overview of the approach.
There are different types of queues here:
Push Based Mechanism:
In a push-based mechanism, the queue takes the responsibility of sending the payload to the processing functions. The consumer functions do not know what is inside the queue until the queue pushes a message to them. RabbitMQ is a classic example of a system that utilises this mechanism**.** This is how it works:
Registeration: Consumer functions (workers) do not have direct access to pull from queue. Instead, they must interact with the queue’s API to register themselves, essentially telling the broker, “I am available for work”.
Broker Management: The Broker stores this registration in an internal database, mapping worker IDs, and then decides which worker receives which message. It handles balancing across multiple workers.
Heartbeats: To ensure workers are still active, the broker requires a continuous “heartbeat”. For example, workers might send an “I am alive” signal every 30 seconds. If a worker fails to send a heartbeat within a specific timeframe, the broker marks it as dead and stops sending it messages.
Pull Based Mechanism:
In a pull-based mechanism, the broker is more passive. There is no central broker managing worker registrations; instead, the control is entirely in the hands of the consumer. AWS SQS (Simple Queue Service) and BullMQQ are prominent examples of pull-based systems.
How it Works
Polling: The workers must actively ask the queue for data. This is typically done using polling mechanisms, such as setting a
setIntervalor running a continuouswhile(true)loop to call the queue’s “pull” API.Manual Processing: The worker pulls a batch of messages, processes them, and then is responsible for explicitly discarding them or putting them back into the queue if they fail.
A massive misconception in system design is that queues always guarantee a strict First-In-First-Out (FIFO) sequence. While pushing into a queue is sequential, the processing is often not.
When you introduce parallel processors (multiple workers with different processing speeds), the order breaks down. For instance, Worker A might pick up Message 1, but a faster Worker B might pick up Message 2 and finish processing it before Worker A is done. This can lead to illogical outcomes, like sending a user a product upgrade prompt before sending their welcome email.
The Solution: To solve this sequence issue in parallel environments, tools like Apache Kafka introduce the concept of partitions and keys. Kafka assigns messages with the same key (e.g., the same User ID) to the same specific worker within a consumer group. This ensures parallelism across different users while maintaining strict, sequential processing for individual user journeys.
When it comes to System Design, everything is a trade-off. Neither Push nor Pull is universally “better” or “worse”.
Choose Push-based (RabbitMQ) if you want a simple architecture with built-in deduplication and routing.
Choose Pull-based (AWS SQS) if you need granular control over the consumption rate and don’t mind the engineering overhead of building back-off and lock mechanisms.
You can even mix both in the same system! For example, using a push queue for immediate notifications and a pull queue for heavy background tasks like video processing.
Understanding these LLD concepts is crucial for mastering asynchronous patterns and succeeding in advanced System Design interviews.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.