Skip to content
>_ StartupJobList_

Interview questions · Worked answers

AWS interview questions for backend and platform engineers

Start from the request or event and trace the whole path: which principal acts on which resource, how data moves, where retries can repeat work, and what signal proves each step succeeded. Strong answers name the AWS service boundary, make failure behavior explicit, and keep security, recovery, latency, and cost tied to the same workload.

For: Backend and platform engineers who build or operate AWS-backed applications

Guide scope and versions

AWS architecture and failure reasoning, not an account deployment guide. The retry algorithm was executed with a controlled local clock and client; the conditional inventory update was executed against DynamoDB Local 3.1.0. These checks do not establish AWS IAM, network, delivery, availability or recovery behavior.

Question 1 of 11

An IAM role appears to allow an S3 read, but the request gets AccessDenied. How do you reason about it?

Trace the exact principal, action, resource, and request context, then inspect the policy types that apply to that principal and resource. Identity policies, resource policies, boundaries, session policies, and organization controls interact differently by principal type and account boundary; any applicable explicit Deny wins.

For example, an ECS task role may allow s3:GetObject on arn:aws:s3:::reports-prod/exports/*, while the application requests s3://reports-prod/private/2026.csv. The resource does not match, so that identity policy grants no matching allow. Inspect bucket/access-point policies, VPC endpoint policy, session policy, permissions boundary, and AWS Organizations controls where they apply. Cross-account access normally needs permission on both the caller and resource-owner sides. For same-account resource-based grants, identify whether the Principal is the IAM role ARN or the assumed-role session ARN: AWS evaluates those grants differently.

A resource-based policy grant to an IAM role ARN remains limited by an implicit deny in a permissions boundary or session policy. A same-account grant directly to an IAM role session ARN grants to that session; AWS documents that such a grant is not limited by implicit denies in the identity policy, boundary, or session policy. This is a specific evaluation case, not a general bypass rule. An explicit Deny in any applicable policy still overrides an Allow. SCPs constrain principals in member accounts; RCPs constrain access to supported resources when enabled and applicable. Capture the actual assumed-role principal and denied action/resource from the service error or CloudTrail, then compare ARN type and request conditions. Check whether conditions such as encryption, source VPC endpoint, tags, or organization membership match.

JSON policy fragments · Worked example
{
  "Effect": "Allow",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::reports-prod/exports/*"
}

{
  "Effect": "Deny",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::reports-prod/exports/restricted/*"
}

If the requested object is under exports/restricted/, the explicit Deny applies even though the Allow also matches. For an object outside exports/*, neither statement grants it. These fragments omit policy version, principal attachment, conditions, and other policy layers; they are for reasoning only.

Architecture example, not an executable AWS integration.

Follow-up: What is the difference between an implicit deny and an explicit deny?

An implicit deny means a particular policy has no applicable Allow. The overall result depends on the policy types being evaluated: an identity-policy Allow can still fail a required boundary or organization-policy allowance. Resource-policy grants to role sessions have the documented same-account exceptions described above. An applicable explicit Deny overrides an Allow; inspect the actual principal, resource and policy layers rather than assuming one policy decides the request.

Read the source: IAM policy evaluation logic · Identity-based and resource-based policies · Permissions boundaries for IAM entities · Service control policies · Resource control policies

↑ Back to questions

Question 2 of 11

A browser must download one private S3 object without receiving AWS credentials. What would you design?

Have a trusted backend authorize the application user and create a short-lived presigned GET URL for one exact object key. The URL uses the signing principal's permissions, acts as a bearer capability, and does not make the bucket public.

The backend should first check that the authenticated application user is entitled to that object, then use a narrowly scoped workload role that can read only the required key set. A presigned URL delegates the signer’s permitted operation to whoever holds the URL until it expires or the underlying credentials stop working. Keep it out of logs, analytics, referrers, and long-lived application records. A URL should be generated on demand, with an expiry that fits the download workflow rather than a broad bucket policy that grants anonymous access.

When a download fails, separate signature problems from authorization and network problems. Confirm the HTTP method, bucket, key, Region, signed headers, and expiry. The signing identity needs permission for the requested operation, and bucket/access-point policy conditions and explicit denies still apply. Block Public Access is a guard against public access granted through S3 ACLs and resource policies, with the most restrictive applicable organization, account, bucket, and access-point settings taking effect; it is not a general deny gate for an authenticated presigned request. If access relies on a public grant that BPA blocks, S3 rejects it. If the object uses SSE-KMS, the request may also depend on KMS authorization and key policy. For uploads, constrain the signed key and method and remember that PUT to an existing key replaces that object, so use a unique server-chosen key or a deliberate versioning/overwrite policy.

Pseudocode · Worked example
user = authenticate_request()
object_key = authorize_download(user, report_id)
url = s3.presign_get(
    bucket="private-reports",
    key=object_key,
    expires_in=300
)
return {"download_url": url}

The important boundary is the application authorization before signing. authorize_download must return a key the user may access, not an arbitrary client-supplied bucket/key. The short expiry is an example policy choice, not a universal value; the SDK call and policy conditions are omitted.

Architecture example, not an executable AWS integration.

Follow-up: Why might a valid presigned URL stop working before its configured expiry?

Temporary credentials used to sign it can expire earlier, or the signing credentials can be revoked. The request can also fail because policy conditions, key permissions, or a relevant explicit deny prevent access.

Read the source: Download and upload objects with presigned URLs · Blocking public access to Amazon S3 storage · S3 SSE-KMS permissions

↑ Back to questions

Question 3 of 11

A queue consumer occasionally charges a customer twice. How do visibility timeout and a DLQ fit the fix?

Assume a message can be delivered more than once and make the business operation idempotent. Set visibility to cover realistic processing time, extend it for longer work where appropriate, and use a redrive policy/DLQ to isolate repeated failures for inspection.

Receiving an SQS message hides it for the visibility timeout; it is not deleted. The consumer deletes it after successful processing. If the worker crashes after charging a payment but before deleting the message, the message can become visible and another worker can process it again. Standard queues use at-least-once delivery, so even a still-hidden message can occasionally be delivered again. The correctness boundary must therefore sit in the application: persist an operation identity and state transition, and use downstream idempotency where the provider supports it.

Visibility timeout reduces simultaneous duplicate work but does not guarantee exactly-once processing. It should account for the tail of processing time, not just the average. If work can run longer, a consumer can extend visibility while still healthy, with a bounded maximum and a recovery path. A redrive policy moves repeatedly received messages to a DLQ after a configured receive count; it does not repair their payloads. Alert on oldest-message age and DLQ growth, inspect the failure cause, and replay only after making the underlying handler safe. FIFO deduplication has a bounded deduplication interval and is not a replacement for durable consumer idempotency.

The worked acknowledgement trace below is for a direct polling consumer: returning without deletion leaves the message available for redelivery after its visibility timeout. Do not copy that return into a Lambda SQS event-source handler, where successful completion can acknowledge the batch. Use the event-source mapping and partial-batch failure contract for that separate integration.

Direct SQS polling consumer pseudocode, not a Lambda event-source handler · Worked example
# Deliberately broken: a durable claim is not proof that a charge happened.
def broken_handle(message):
    key = message["payment_request_id"]
    if not ledger.claim_once(key):
        return  # can suppress a request that crashed before charging
    charge_provider(idempotency_key=key)
    ledger.mark_complete(key)
    delete_sqs_message(message)

# Illustrative recovery shape; provider API and storage are intentionally abstract.
def handle(message):
    op = ledger.create_or_load_pending(message["payment_request_id"])
    if op.state == "SUCCEEDED":
        delete_sqs_message(message)
        return
    if not ledger.acquire_or_reclaim_expired_lease(op.id):
        return  # another live worker owns it; allow retry after visibility timeout
    outcome = provider.lookup_by_idempotency_key(op.provider_key)
    if outcome is None:
        outcome = provider.charge(
            request=op.immutable_charge_request,
            idempotency_key=op.provider_key
        )
    ledger.record_provider_outcome_if_lease_owner(op.id, outcome)
    if ledger.is_succeeded(op.id):
        delete_sqs_message(message)
    # If outcome remains ambiguous, keep PENDING for reconciliation; do not invent success.

The broken version can suppress the intended charge: it writes the claim, crashes before calling the provider, then a redelivery sees 'claimed' and exits. It can also leave an ambiguous result if the provider completed the charge but the worker crashed before recording success. The recovery trace is PENDING -> leased -> provider request with stable key -> SUCCEEDED -> delete message; an uncertain provider outcome remains PENDING for reconciliation, and a duplicate that sees SUCCEEDED is acknowledged without charging again. Create the PENDING record uniquely, and make lease transitions and result writes conditional on the current owner so a stale worker cannot overwrite newer state. The sketch uses one durable operation ID, an expiry lease, and the same immutable request/provider key on each attempt. A retry first reconciles with the provider, then records SUCCEEDED before deleting the SQS message. Lease transitions and result writes must be conditional on the current owner so a stale worker cannot overwrite newer state. This is an application design sketch, not an integration or a claim of exactly-once delivery. It is safe to resubmit only when the chosen provider documents idempotency for that operation and key within its stated scope and retention window. If the outcome is ambiguous after that guarantee expires, reconcile through provider records or a manual recovery path; do not blindly charge again. Without provider idempotency or a reliable lookup, exactly-once external side effects cannot be promised.

Architecture example, not an executable AWS integration.

Follow-up: What if normal processing sometimes exceeds the visibility timeout?

Use measured tail duration to set an appropriate starting timeout and extend visibility while processing continues, with limits and heartbeats. Keep the operation recoverable: persist PENDING before the side effect, use a lease to coordinate attempts, reconcile ambiguous provider outcomes with the same stable key, and mark success before deleting the message. A visibility extension can fail or the worker can lose ownership, so idempotency and stale-owner checks remain necessary.

Read the source: Understanding SQS visibility timeout · Processing SQS messages in a timely manner · SQS outage recovery scenarios

↑ Back to questions

Question 4 of 11

A Lambda handler is invoked from an API, an asynchronous event, and a queue. Are the retry rules the same?

No. Synchronous invocation returns an outcome to its caller, asynchronous invocation uses Lambda's managed queue and retry policy, and event source mappings poll a source and apply source-specific batch, visibility, checkpoint, and retry behavior.

For a direct synchronous Invoke or a typical request/response integration, the caller sees the function result or error and owns its retry policy. A timeout can leave the caller uncertain whether side effects completed. For asynchronous invocation, Lambda accepts the event into its internal queue and retries function errors by default; throttling and service errors have different retry behavior and events can be delivered more than once. Configure failure destinations or a DLQ when preserving failed events matters, and make handlers idempotent.

An event source mapping is different again. With SQS, Lambda polls and invokes with batches; by default a failed batch can return all its messages, while partial batch responses can identify only failed records. A stream mapping can retry a batch and block a shard behind a poison record until it succeeds, expires, or configured error handling advances it. The source's visibility timeout or retention window, batch size, maximum age/attempts, and destination shape the recovery contract. Ask which exact invocation path, source service, and batch behavior the system uses before proposing 'retry twice' as a universal setting.

Decision table · Worked example
Path                         Retry owner / key concern
Direct synchronous request   Caller; response may be ambiguous after timeout
Asynchronous Invoke          Lambda queue; duplicate delivery and async retry policy
SQS event source mapping     Queue visibility/redrive plus batch and partial failures
Stream event source mapping Source retention/checkpoint; failed records can block progress

This is a classification aid, not a complete inventory. Individual AWS services can invoke synchronously or asynchronously and may add their own retry behavior. Verify the integration path and service-specific documentation before setting retry counts or deadlines.

Architecture example, not an executable AWS integration.

Follow-up: What would you monitor for a Lambda function consuming SQS?

Correlate function errors and throttles with queue visible-message count, age of oldest message, receive counts, DLQ arrivals, concurrency, and processing latency. A growing queue age with falling throughput distinguishes backlog from a single failed invocation.

Read the source: Lambda invocation methods · Understanding retry behavior in Lambda event functions · Handling errors for an SQS event source in Lambda

↑ Back to questions

Question 5 of 11

Two requests race to reserve the last seat in DynamoDB. How do you prevent overselling?

Put the availability check and state change in a conditional write, so DynamoDB evaluates the condition against the current item version. Do not read the count, decide in application code, and then issue an unconditional update.

Suppose a reservation item stores remaining = 1. Two handlers both read one, then each writes zero and confirms a reservation. The final value looks plausible but two customers received the seat. A conditional update such as remaining >= :one with SET remaining = remaining - :one lets only a request whose condition still holds succeed. Treat a conditional failure as a business conflict and return a clear unavailable result, not as a transient error to retry forever.

Retries create another ambiguity: a client can lose the response after the write succeeded. A stable request ID and conditional reservation record can prevent the same request from applying twice. If the operation spans multiple items, TransactWriteItems can group supported actions atomically, but transaction conflicts, validation, capacity, and idempotency-token lifetime still matter. Keep the idempotency key's scope and retention aligned with the business operation; an expired token or a new request ID does not deduplicate a logical duplicate.

Python, boto3 DynamoDB client · Worked example
def reserve_one(client, table_name, item_id):
    return client.update_item(
        TableName=table_name,
        Key={"id": {"S": item_id}},
        UpdateExpression="SET remaining = remaining - :one",
        ConditionExpression="remaining >= :one",
        ExpressionAttributeValues={":one": {"N": "1"}},
        ReturnValues="UPDATED_NEW",
    )

The conditional update prevents the count from moving below zero under concurrent requests, assuming the key identifies the single inventory item and the caller handles a failed condition. A production design also records the request/reservation identity and handles retry-after-timeout safely; this expression alone does not provide an end-to-end exactly-once guarantee. This low-level boto3 client uses typed DynamoDB attribute values. The client and table are supplied by the caller; handle ConditionalCheckFailedException separately from transport failures.

Executed against DynamoDB Local 3.1.0 using synthetic inventory.

Follow-up: When would you use a transaction instead of one conditional update?

Use a transaction when the invariant spans multiple items or actions that must succeed or fail together. Prefer the single-item conditional write when it fully expresses the invariant; transactions add constraints and consumed capacity that should be justified by the consistency requirement.

Read the source: DynamoDB conditional writes · How DynamoDB transactions work · DynamoDB read and write operations

↑ Back to questions

Question 6 of 11

A product asks for Multi-AZ RDS. Does that also give read scaling and protection from accidental deletion?

Clarify the deployment type and recovery objective. A traditional Multi-AZ DB instance deployment is for availability/failover, not read scaling; backups and point-in-time recovery address data recovery, while read replicas serve a different read-scaling or replication purpose.

For a Multi-AZ DB instance deployment with one standby, RDS maintains synchronous standby replication in another Availability Zone and can fail over. The standby is not a read endpoint. Multi-AZ DB clusters have a different topology and can include readable standby instances, so 'Multi-AZ' alone is not enough to answer the read-capacity question. A read replica is separately configured and generally follows asynchronously, which means its lag and promotion behavior need to be included in the design.

High availability is not a backup strategy. A replica may reproduce an accidental delete or corrupt write, and failover can make the same unwanted change available on the new primary. Automated backups and point-in-time recovery provide a restore path within the configured retention period; restoring creates a separate DB instance that must be validated and cut over deliberately. State the recovery point objective and recovery time objective, test restoration, and distinguish AZ failure, Region failure, operator error, and logical corruption.

Decision sketch · Worked example
Need automatic failover after an AZ/instance outage?  -> Multi-AZ design
Need additional read capacity?                       -> Read replica or readable cluster topology
Need recovery after mistaken DELETE at 14:03?        -> PITR/backup retention and tested restore
Need Region-level recovery?                           -> Cross-Region recovery design

The arrows separate objectives, not prescribe a specific configuration. Exact engine/version support and recovery behavior vary by RDS deployment type and Region; verify the chosen engine's current documentation and rehearse recovery against the required RPO/RTO.

Architecture example, not an executable AWS integration.

Follow-up: How would you prove the backup is useful?

Run a restore into an isolated target, verify application-level integrity and required dependencies, measure time to a usable service, and document the tested recovery point. A successful backup job alone does not demonstrate that the application can recover within its objectives.

Read the source: Multi-AZ DB instance deployments for Amazon RDS · Amazon RDS read replicas · Amazon RDS backups and restore

↑ Back to questions

Question 7 of 11

A private ECS task cannot reach S3 or a public API. How do you trace the network path?

Trace DNS and the route from the task subnet to the destination, then check security-group egress/return state, subnet network ACLs, endpoint policy, and the destination's authorization. A private subnet is a routing and addressing property, not a promise of access to every AWS service.

For an S3 call, first confirm name resolution and the task's actual subnet and route table. A gateway VPC endpoint can route S3 traffic privately when associated with the relevant route tables; its endpoint policy can further restrict principals or resources. Without a service endpoint, IPv4 internet access from a private subnet commonly traverses a NAT gateway and a route to it. A route table selects a target; it does not grant IAM permission or open a security group.

Security groups are stateful and allow rules only; return traffic for an allowed connection is tracked. Network ACLs apply at subnet boundaries, are stateless, and require appropriate inbound and outbound rules, including return traffic. For a failed connection, use the five-tuple and timestamp to correlate resolver output, route choice, VPC Flow Logs where available, endpoint/network policy, and application error. Separate DNS failure, no route, blocked packet, TLS/endpoint issue, and service-level AccessDenied; each points to a different layer.

Network path trace · Worked example
Task ENI in private subnet
  -> subnet route table: S3 prefix-list route to gateway endpoint
  -> endpoint policy and task-role authorization
  -> S3 bucket/object policy and encryption-key policy

Task ENI to external API over IPv4
  -> private-subnet default route to NAT gateway
  -> NAT subnet route to internet gateway
  -> remote API; response returns over tracked connection

The path is conceptual; actual route tables, endpoints, NAT placement, DNS behavior, and address family must be checked in the target VPC. A gateway endpoint route is more specific than a default route when the destination matches its prefix list.

Architecture example, not an executable AWS integration.

Follow-up: What is the main difference between a security group and a network ACL here?

A security group is a stateful allow-list attached to network interfaces. A network ACL is a stateless subnet-level filter that supports allow and deny rules, so both directions must be considered.

Read the source: VPC security groups and network ACLs · VPC route table concepts · VPC routing options, NAT and gateway endpoints

↑ Back to questions

Question 8 of 11

An ECS service keeps replacing tasks after a release. What lifecycle evidence would you inspect?

Separate task launch from task health and service steady state. Inspect stopped reasons and container exit codes, image and secret retrieval, capacity placement, container health checks, load-balancer target health, deployment events, and the circuit-breaker state.

An ECS task definition describes containers, resources, networking, roles, logging, and related runtime settings. A task is one running instance of that definition; an ECS service maintains a desired number of tasks and replaces tasks that stop. A task reaching RUNNING does not necessarily mean the application is ready for user traffic. Container health checks and, when attached, load-balancer health checks affect service health and deployment progress.

During a rolling deployment, a new task may fail before it starts because of placement or image retrieval, or start and then fail health checks. The deployment circuit breaker can mark a deployment failed and, when configured with a prior completed deployment, roll back. Inspect the service deployment's rollout state/reason and EventBridge events alongside task stopped reasons. Do not assume rollback reverses a database migration or a message side effect; application and schema compatibility still govern whether the previous task revision can safely serve.

Triage sequence · Worked example
1. Service events: placement, deployment, steady-state transitions
2. Task details: stopped reason, container exit code, image/secret/network errors
3. Health: task-definition container check and load-balancer target health
4. Capacity: desired/running count, subnet addresses, launch capacity
5. Release boundary: old/new task definition and schema compatibility

This sequence narrows the failing lifecycle stage. Exact events and health behavior differ between launch types and service configuration; it is a diagnostic order, not a command run against a cluster.

Architecture example, not an executable AWS integration.

Follow-up: Why might an image-level Docker health check not be visible to ECS?

ECS monitors health checks specified in the task definition. A health check embedded only in the image is not necessarily monitored unless it is represented in the container definition.

Read the source: Amazon ECS task definitions · Determine ECS task health using container health checks · ECS deployment circuit breaker

↑ Back to questions

Question 9 of 11

A service is slow, but its error rate is flat. What would you instrument and correlate?

Measure the user-visible request path with latency distributions, traffic, errors, saturation, and dependency timing, then add structured logs and trace context that connect a request to its downstream calls. A flat error rate does not show whether requests are queued, throttled, or waiting on a dependency.

For a synchronous API, record request rate and latency percentiles, status class, queue or connection-pool wait, and dependency spans for DynamoDB, RDS, S3, or an external API. Include a stable request/trace ID and safe resource identifiers, but do not log credentials, presigned URLs, or sensitive payloads. Use low-cardinality metric dimensions such as operation and service; unique customer IDs or request IDs belong in logs/traces, not metric labels that create unbounded series.

For a queue-backed path, compare age of oldest SQS message, visible and in-flight messages, Lambda errors/throttles/concurrency, batch duration, and DLQ arrivals. For ECS, inspect task health, CPU/memory, restarts, deployment events, and load-balancer target health. A rising queue age plus falling consumer concurrency suggests a different failure from normal processing with a slow downstream database. Define alerts around a user or service objective and a sustained condition, then use dashboards and traces to identify the constrained dependency rather than treating every metric as an alert.

Signal correlation · Worked example
Queue age rises + visible messages rise + Lambda errors rise
  -> inspect failed batch, poison record, and downstream dependency

Queue age rises + throttles rise + errors stay low
  -> inspect concurrency limits, duration, and event-source scaling

API p99 rises + DB span rises + request volume is steady
  -> inspect database load, pool wait, query plan, and connection saturation

The relationships are diagnostic hypotheses, not proof. Validate each with traces, logs, service metrics, and a time-aligned deployment or dependency event. Averages can hide tail latency; use distributions and a bounded time window.

Architecture example, not an executable AWS integration.

Follow-up: What is the minimum useful trace boundary for a request that crosses an API, queue, and worker?

Propagate a correlation or trace context through request acceptance, message publication, and worker processing, while preserving the original operation ID and recording separate spans and outcomes. Queue delay and worker execution time should remain distinguishable.

Read the source: What is Amazon CloudWatch? · Troubleshoot Lambda event source mappings · Amazon ECS task lifecycle events

↑ Back to questions

Question 10 of 11

Three layers retry a downstream call. How do you stop a transient outage becoming a retry storm?

Assign one bounded retry budget across the request path, use backoff with jitter for retryable failures, and propagate a deadline so inner work cannot outlive the caller's useful time. Make side effects idempotent and ensure the layer that owns the event source handles exhaustion explicitly.

If an API retries three times, a Lambda retries twice, and an SDK also retries, one user action can produce many downstream attempts. During an outage, those attempts consume the same constrained capacity needed for recovery. Draw the call chain, identify which layer owns each retry, classify retryable versus permanent errors, and calculate the maximum attempt count and time spent against the request deadline. Use exponential backoff with jitter where the service/client behavior supports it, cap attempts and elapsed time, and avoid retries for validation or authorization failures.

For asynchronous work, there may be no caller waiting, so the budget becomes a queue age/retention objective plus an operational redrive path. Record attempt count and original event identity. When exhausted, send the event to a DLQ or failure destination with enough context for controlled replay. Idempotency protects repeated execution, but it does not make an unbounded retry storm cheap or fast; circuit breaking, concurrency limits, load shedding, and queue backpressure address pressure on the dependency.

Python · Worked example
def call_with_budget(request, now, sleep, call_dependency, backoff_with_jitter):
    deadline = request.deadline
    usable_deadline = deadline - RESPONSE_RESERVE
    for attempt in range(MAX_ATTEMPTS):
        remaining = usable_deadline - now()
        if remaining <= 0:
            break
        per_attempt_timeout = min(CLIENT_TIMEOUT_CAP, remaining)
        try:
            # The client adapter must enforce this timeout, including connect/read phases.
            return call_dependency(
                idempotency_key=request.operation_id,
                timeout=per_attempt_timeout
            )
        except RetryableError:
            remaining = usable_deadline - now()
            if remaining <= 0:
                break
            sleep(min(backoff_with_jitter(attempt), remaining))
    raise RetryBudgetExhausted()

The sketch only enforces the deadline if the client adapter actually applies timeout to the network attempt and returns control by that limit; some clients need separate connect/read/write caps or cancellation support. If the adapter cannot enforce an attempt timeout, the code cannot claim a hard end-to-end bound. The sleep is capped by the remaining usable time, which already excludes the response reserve. Define retryable errors, caps, and idempotency support for the real client and dependency. A timeout can still race with a completed operation whose response was lost.

Executed locally with a controlled Python clock and client, not an AWS service.

Follow-up: When should a queued event stop retrying and move to a DLQ?

When its retry count, age, or processing policy is exhausted and automatic attempts are no longer useful. Preserve enough metadata to diagnose and replay it safely after correcting the cause; avoid repeatedly requeueing it without a recovery limit.

Read the source: Lambda retry behavior by invocation type · SQS visibility timeout and redelivery · AWS Well-Architected reliability design principles

↑ Back to questions

Question 11 of 11

How would you review AWS cost for a growing service without guessing prices or promising savings?

Model the workload in service-specific units, measure where usage and data move, and then apply current account, Region, pricing, and commitment data. Name the dominant meter and the workload driver before proposing a change.

Start with a request or job unit: requests per second, average and tail duration, memory/configuration, records or bytes per message, database reads/writes and item size, storage retained, log volume, and bytes crossing network boundaries. For a queue worker, estimate executions per message plus retries and the duration distribution. For RDS, separate instance/runtime capacity, storage, I/O characteristics, backups, and replica topology. For data movement, map traffic by source, destination, Region/AZ, NAT, gateway endpoint, and internet egress. These dimensions let a team compare architecture choices without asserting a price that may be wrong for the current account or Region.

Instrument actual usage and reconcile it with billing and cost-allocation views. A NAT gateway may be a useful path for general outbound IPv4 traffic, while a gateway endpoint can route supported S3 traffic privately and avoid sending it through NAT; the choice still has operational and endpoint policy implications. Data transfer can be a material cost category, so reduce unnecessary cross-boundary bytes, repeated payload fetches, and verbose log retention only after checking the reliability and audit needs. Use the current AWS Pricing Calculator and account billing data for numbers, and compare total cost per successful business operation rather than one service's unit price in isolation.

Cost model worksheet · Worked example
For a shared billing window and declared allocation rules:
Cost per successful job =
  (compute charges + queue charges + database charges
   + observability charges + network-transfer charges
   + allocated shared charges) / successful jobs

Report the underlying meters separately: invocations, retries,
requests, reads/writes, GB transferred, and retained days.

The numerator must sum monetary charges for a consistent billing window, not add unlike meter units such as invocations, bytes, and database reads. Define how shared charges are allocated, and compare the result only across workloads with the same period and allocation method. Actual charges depend on service, Region, account discounts, free tiers, capacity mode, data path, and current rates. Use billing data and the current pricing calculator for numeric estimates; this makes no price or savings claim.

Architecture example, not an executable AWS integration.

Follow-up: Why track cost per successful operation instead of cost per invocation?

A failing workload may produce more retries and invocations while completing fewer useful operations. Cost per success includes the effect of reliability, duplicate work, and data movement on the business outcome.

Read the source: AWS Well-Architected cost optimization pillar · Plan for data transfer · VPC routing options for NAT and gateway endpoints · AWS Pricing Calculator

↑ Back to questions

Salary context

Advertised salaries for these skills

These are Hacker News job posts that mention these technologies, not employee earnings or a measure of what a skill adds to pay. The amounts summarize annualized advertised minimums in approximate USD. Groups can overlap and differ in roles, locations and dates.

AWS posts

283 posts · 283 usable advertised salary amounts

2024-06-04 to 2026-10-14

$150,000

Median advertised minimum, approximate USD/year

Middle 50%: $115,000–$175,000

Explore AWS salaries → Browse matching job posts →
Currency and calculation basis

Approximate USD using ECB daily reference rates as of 2026-10-09. Original currencies and offer ranges remain on the source listings. These figures cover posts tagged AWS, rather than every job in the broader technology family.

↑ Back to questions

Keep exploring