How Google Cloud Pub/Sub and Amazon SQS differ on fan-out, delivery, ordering, message limits and pricing, and how to write queue code that runs on either one.
Google Cloud Pub/Sub and Amazon SQS both move messages between parts of a backend, but they start from different models. Pub/Sub is built around topics, and every subscription gets its own copy of each message. SQS is a queue, where one consumer takes each message, and fan-out comes from putting SNS in front of it.
That difference decides how much infrastructure a simple "order placed, now ship it and email the customer" flow needs. This guide compares the two services on the points that matter for an application backend, and shows how Encore lets you write the flow once and run it on either cloud.
Each option is covered below, starting with Encore.
Encore combines an open source infrastructure SDK for TypeScript and Go with a platform that provisions and deploys into your own AWS or GCP account. Pub/Sub is declared in code: a topic with an event type, and subscriptions with handlers. In GCP environments Encore creates Google Cloud Pub/Sub topics and subscriptions with dead-letter topics. In AWS environments the same code becomes SNS topics with SQS queues and dead-letter queues, and Encore provisions an IAM role for each service.

Here an orders service publishes an event and a shipping service reacts to it:
// orders/events.go
package orders
import "encore.dev/pubsub"
type OrderPlaced struct {
OrderID int64 `json:"order_id"`
Email string `json:"email"`
}
// Google Cloud Pub/Sub on GCP, SNS and SQS on AWS.
var Placed = pubsub.NewTopic[*OrderPlaced]("order-placed", pubsub.TopicConfig{
DeliveryGuarantee: pubsub.AtLeastOnce,
})
// shipping/shipping.go
package shipping
import (
"context"
"encore.app/orders"
"encore.dev/pubsub"
"encore.dev/storage/sqldb"
)
var db = sqldb.NewDatabase("shipping", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
var _ = pubsub.NewSubscription(orders.Placed, "create-shipment",
pubsub.SubscriptionConfig[*orders.OrderPlaced]{
Handler: createShipment,
},
)
// Delivery is at-least-once, so a redelivered event must not create a second shipment.
func createShipment(ctx context.Context, e *orders.OrderPlaced) error {
_, err := db.Exec(ctx, `
INSERT INTO shipments (order_id, status) VALUES ($1, 'pending')
ON CONFLICT (order_id) DO NOTHING`, e.OrderID)
return err
}
The migration in shipping/migrations creates the shipments table the queries use:
-- shipping/migrations/1_create_shipments.up.sql
CREATE TABLE shipments (
order_id BIGINT PRIMARY KEY,
status TEXT NOT NULL
);
Run in your terminal to get started locally.
A second subscription, say send-receipt in an email service, gets its own copy of every event with its own retries and dead letters. On GCP that is another Pub/Sub subscription, and on AWS another SQS queue subscribed to the SNS topic, with no extra code beyond the handler. encore run delivers events in process and shows each publish and handler call in the local trace view.
An agent adding the receipt email writes one handler and one subscription in Go, and tests it locally.
Pub/Sub delivers each message published to a topic to every subscription attached to it. Subscribers can pull messages or have Pub/Sub push them to an HTTPS endpoint, which suits Cloud Run. Ordering keys keep messages with the same key in order, dead-letter topics catch messages that keep failing, and exactly-once delivery is available for pull subscriptions.
Per the Pub/Sub quotas, messages can be up to 10 MB and retained for up to 31 days. Pricing is based on the volume of data published and delivered, per Pub/Sub pricing, so many small messages are cheap and large payloads add up.
SQS stores messages until a consumer receives and deletes them. A visibility timeout hides a received message from other consumers while it is processed, and a redrive policy moves it to a dead-letter queue after a set number of failures. Standard queues offer high throughput with occasional duplicates and loose ordering, and FIFO queues give strict ordering per message group with deduplication.
Per the SQS message quotas, messages can be up to 1 MiB and retained for up to 14 days. Pricing is per request, per SQS pricing. For fan-out you add an SNS topic and subscribe one queue per consumer, each with a queue policy allowing SNS to send to it.
Both services deliver at least once in their default modes, so handlers should be idempotent no matter which you pick. A unique constraint and ON CONFLICT DO NOTHING, as in the shipping handler above, is usually enough.
Pick Encore when you want topics and subscriptions defined next to the code that uses them, with dead letters created for you, and the option to run the same code on Google Cloud Pub/Sub or on SNS and SQS.
Pick Google Cloud Pub/Sub directly when you are on GCP and want push delivery to Cloud Run or messages larger than 1 MiB. Pick SQS directly when you are on AWS and need a simple work queue for one consumer, or FIFO ordering with deduplication.
Google Cloud Pub/Sub is a topic-based messaging service: publishers send to a topic and every subscription gets its own copy, delivered by pull or push. Amazon SQS is a queue: each message is consumed by one receiver, and fan-out to several consumers needs SNS in front of multiple SQS queues.
Functionally close. A Pub/Sub topic with several subscriptions behaves like an SNS topic fanning out to several SQS queues, each with its own backlog and retries. The differences are in the details: Pub/Sub messages can be up to 10 MB and kept for up to 31 days, while SQS messages are up to 1 MiB and kept for up to 14 days.
With Encore, you declare a topic with pubsub.NewTopic in Go or new Topic in TypeScript and add subscriptions in code. In a GCP environment Encore creates Google Cloud Pub/Sub topics and subscriptions with dead-letter topics, and in an AWS environment it creates SNS topics with SQS queues and dead-letter queues. Locally, encore run uses an in-process implementation.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.