The "Invisible" Message
When a consumer (a Lambda function, an EC2 instance, or an ECS task) polls a message from SQS, the message is not deleted from the queue. SQS uses a two-phase approach: first hide the message, then delete it only after the consumer confirms success. This protects against consumer crashes.
The period during which the message is hidden from all other consumers is called the Visibility Timeout. The default is 30 seconds. The maximum is 12 hours.
The Full Flow
- T=0s: Consumer A calls
ReceiveMessage. SQS returns Message X and starts the Visibility Timeout timer. Message X is now invisible to all other consumers. - T=0s to Timeout: Consumer A processes the job.
- Success path: Consumer A finishes at T=5s and calls
DeleteMessagewith the receipt handle SQS provided. Message X is permanently removed. Done. - Crash path: Consumer A crashes at T=10s. It never calls
DeleteMessage. - T=30s: The Visibility Timeout expires. Message X reappears in the queue as if nothing happened.
- T=31s: Consumer B polls the queue, receives Message X, and retries processing.
This mechanism is what makes SQS resilient to consumer failures — messages are never truly lost unless explicitly deleted.
Setting the Right Visibility Timeout
The critical rule: the Visibility Timeout must be longer than the maximum time your consumer could take to process a single message.
If your Lambda function has a 15-minute timeout and processes large files, set the Visibility Timeout to at least 15 minutes. A common mistake is leaving it at the 30-second default.
For variable-duration jobs, consumers can call ChangeMessageVisibility to extend the timeout while they're still working — a heartbeat pattern that prevents premature re-queuing for long-running tasks.
A common interview scenario: "Your SQS consumers are processing messages, but you're seeing duplicate processing and users are getting duplicate notifications. What's wrong?" The answer is almost always a Visibility Timeout that is shorter than the processing time. Walk through the timeline: the message becomes visible again before the first consumer finishes, a second consumer picks it up, and both complete successfully — causing duplicate side effects. The fix is increasing the Visibility Timeout or implementing idempotency checks.