SQS long polling doesn’t actually eliminate empty receives, it just makes them less frequent and cheaper.

Let’s see it in action. Imagine you have a queue named my-processing-queue. Normally, if you poll it without long polling, you might get an empty response back almost immediately if there are no messages. This is a "short poll."

aws sqs receive-message --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-processing-queue

If the queue is empty, you get this:

{
    "Messages": []
}

This costs you API calls and CPU cycles on your end to process the empty response.

Now, let’s enable long polling. We’ll set a WaitTimeSeconds of 20. This tells SQS to hold onto the request for up to 20 seconds, waiting for a message to arrive.

aws sqs receive-message --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-processing-queue --wait-time-seconds 20

If no messages arrive within those 20 seconds, you still get an empty response:

{
    "Messages": []
}

But the crucial difference is that SQS waited. You only get an empty response after the specified WaitTimeSeconds has elapsed. This drastically reduces the number of API calls you make when the queue is idle, and consequently, your AWS bill for SQS API requests.

The mental model here is that SQS becomes a bit like a waiter. With short polling, the waiter just shrugs and says "nothing here" the moment you ask. With long polling, the waiter says "hold on a sec, I’ll check again" and waits for a reasonable time before coming back empty-handed.

The problem this solves is the cost and inefficiency of constantly polling an empty queue. Applications that process messages at an irregular rate can rack up huge bills with short polling if they need to maintain low latency. Long polling smooths this out. You’re effectively paying SQS to hold the connection open, rather than paying for your own polling loops to spin and check.

The core levers you control are the WaitTimeSeconds parameter. This can be set between 0 and 20 seconds. A higher value means fewer, but potentially longer, waits for messages. A lower value means more frequent polling, but shorter waits. You also control this at the queue level when you create or modify the queue. If you set ReceiveMessageWaitTimeSeconds when creating the queue:

aws sqs create-queue --queue-name my-processing-queue --attributes '{ "ReceiveMessageWaitTimeSeconds": "20" }'

Then all receive-message calls to that queue will automatically use a 20-second wait time, even if you don’t specify --wait-time-seconds.

The surprising thing is how much impact this seemingly small change has on cost. For a queue that receives messages infrequently, say once every few minutes, and is polled every 5 seconds with short polling, you’re making 12 API calls per minute. With long polling set to 20 seconds, you’re making at most 3 API calls per minute (one every 20 seconds), a 75% reduction. Over a month, this adds up.

The underlying mechanism is that SQS uses a distributed polling service. When you request long polling, your receive-message request is not immediately answered. Instead, it’s queued and associated with a long-polling request ID. SQS then monitors the queue. If a message arrives, it’s delivered to one of the waiting long-polling requests. If the WaitTimeSeconds expires without a message, the request is completed with an empty response. This is managed by SQS’s internal infrastructure, so your application doesn’t need to manage the waiting itself.

The next concept you’ll encounter is how to handle the visibility timeout in conjunction with long polling, especially when messages might take longer to process than the visibility timeout.

Want structured learning?

Take the full Sqs course →