backend / cloud aws / _interview_essentials.md

AWS Interview Essentials — Cross-Service Decision Matrices

6 interview angles 10 min read source

AWS Interview Essentials — Cross-Service Decision Matrices

The cross-cutting content interviewers ask about most. Individual service files (Compute/, Databases/, etc.) have the depth; this file is the “which one and why” layer plus the patterns that span services.

1. Messaging: SNS vs SQS vs EventBridge vs Kinesis vs MSK

SQS SNS EventBridge Kinesis Data Streams MSK (managed Kafka)
Pattern queue (1 consumer per msg) pub/sub fan-out event bus + routing ordered stream + replay ordered stream + replay
Consumers competing each subscriber gets a copy rule-matched targets many readers, own offset many readers, own offset
Ordering FIFO queues only FIFO topics only no per-shard per-partition
Replay no (acked = gone) no archive + replay retention window (≤365d) retention (configurable)
Filtering no subscription filter policies rich event-pattern matching consumer-side consumer-side
Throughput ~unlimited (standard) ~unlimited thousands/s shards × 1MB/s in very high
Latency ms ms ms ms ms
Cost model per request per request per event published per shard-hour + payload per broker-hour
Best for work distribution, buffering fan-out notifications event routing across many types/targets, SaaS integration, replay high-throughput streaming, multiple independent readers, ordered replay Kafka-compatible streaming, existing Kafka tooling

Decision shortcut:

  • Two services, reliable handoff → SQS.
  • Tell N services “this happened” → SNS.
  • Route many event types to many targets with filtering / archive-replay / partner SaaS → EventBridge.
  • High-throughput ordered stream, replay, multiple independent consumers → Kinesis (or MSK if you need Kafka APIs/tooling).
  • “Exactly once across DB + queue” → none of these alone; use transactional outbox + idempotent consumers.

See Application_Integration/ for SQS/SNS/EventBridge depth.

2. Compute: Lambda vs Fargate vs ECS-on-EC2 vs EKS vs EC2 vs App Runner vs Batch

Lambda Fargate ECS-on-EC2 EKS EC2 App Runner Batch
Model function, event-driven container, serverless container, you own nodes container, K8s VM container, fully managed web batch jobs
Max duration 15 min unbounded unbounded unbounded unbounded unbounded unbounded
Cold start yes (ms–s) ~30-60s task start node + task node + pod minutes (boot) yes job queue latency
Scales to zero yes no (task min 1) no no no yes yes (queue empties)
Ops burden lowest low medium high (K8s) high lowest low
Cost shape per invocation + GB-s per task vCPU/RAM-s EC2 + you bin-pack EC2/Fargate + $73/mo control plane per instance-hour per request + compute per job compute
Best for spiky / event-driven / short web services + workers, no node mgmt steady high-volume, cost-optimized, GPU K8s shops, multi-cloud, rich ecosystem full control, special hardware, legacy simple containerized web app scheduled / parallel batch (rendering, ETL)

Decision shortcut:

  • Event-driven, short, spiky → Lambda.
  • Long-running web service / worker, don’t want to manage nodes → Fargate.
  • Steady high-volume where per-CPU cost matters, or GPU → ECS-on-EC2 (Spot + Reserved).
  • Already K8s / multi-cloud / need the K8s ecosystem → EKS.
  • Simple “deploy my container as a web app” → App Runner.
  • Big parallel batch jobs → Batch.

See Compute/01_Compare_AWS_Compute_Services/README.md and individual files.

3. Datastore: RDS vs Aurora vs DynamoDB vs ElastiCache vs DocumentDB vs OpenSearch

RDS Aurora DynamoDB ElastiCache DocumentDB OpenSearch
Type managed SQL (PG/MySQL/…) AWS SQL engine managed NoSQL KV/doc managed Redis/Memcached MongoDB-compatible search + analytics
Consistency strong strong eventual default, strong opt-in n/a (cache) tunable eventual
Scaling vertical + read replicas storage auto, 15 readers horizontal, automatic cluster mode sharding replica set shards
Access pattern ad-hoc SQL, joins ad-hoc SQL, joins known key access patterns get/set, TTL, structures document queries full-text, aggregations
Failover 60-120s (Multi-AZ) ~30s n/a (managed) seconds seconds n/a
Best for relational workloads, joins, transactions same but bigger/faster/HA scale, predictable latency, single-digit ms hot-path caching, sessions, rate limits Mongo workloads on AWS search, log analytics, vector search

Decision shortcut:

  • Relational, joins, transactions, moderate scale → RDS.
  • Same but need faster failover, more read replicas, or storage > 64 TB → Aurora.
  • Known access patterns, massive scale, predictable latency → DynamoDB.
  • Sub-ms cache in front of any of the above → ElastiCache.
  • You have MongoDB code → DocumentDB (or self-host / Atlas).
  • Full-text search, log analytics, vector search → OpenSearch.

4. Request flow walkthrough

A typical production request, with the layers identified:

User


Route 53            DNS resolution; latency/failover routing


CloudFront          edge cache; static assets served here; TLS termination
  │  (cache miss / dynamic)

AWS WAF             rate limiting, SQLi/XSS rules, geo / IP blocking


ALB  or  API Gateway
  │  ALB: L7 routing, ECS/EKS targets, OIDC auth
  │  API GW: REST/HTTP API, Cognito/Lambda authorizers, throttling, usage plans

Compute             ECS/Fargate task  OR  Lambda
  │  - auth already validated at the edge / gateway
  │  - app reads secrets from Secrets Manager / SSM at cold start

Data layer
  │  - RDS via RDS Proxy (Lambda) or direct pool (long-running)
  │  - DynamoDB direct
  │  - ElastiCache checked first (cache-aside)

Async side-effects  → SQS / SNS / EventBridge → workers

Where caching lives: CloudFront (edge), ElastiCache (app data), API Gateway cache (rarely worth it). Where auth lives: WAF (coarse), gateway authorizer (token validation), app (fine-grained authz). Observability: CloudWatch Logs/Metrics + X-Ray traces threaded through every hop.

5. The “Lambda + RDS” problem

The single most-asked AWS+backend interview question.

The problem: Lambda scales by spawning concurrent execution environments. Each one opens its own DB connections. 1000 concurrent Lambdas → 1000 Postgres connections → RDS hits max_connections, refuses new connections, the whole service fails. Postgres connections are expensive (a process + memory each); a db.t3.medium caps around ~340 connections.

Three solutions:

Solution How Trade-off
RDS Proxy managed connection pooler in front of RDS; Lambda connects to the proxy, proxy multiplexes a small pool to RDS best general fix; ~$0.015/h per vCPU of the DB; pinning — prepared statements, session-level SET, advisory locks, LISTEN/NOTIFY force a connection to “pin” and stop multiplexing
Careful concurrency control set Lambda reserved concurrency low enough that max concurrent × connections-per-Lambda ≤ RDS capacity; reuse the connection across invocations (open it module-level, outside the handler) caps throughput; brittle as the system grows
Move to DynamoDB DynamoDB has no connection concept — it’s an HTTPS API; scales with Lambda naturally only works if the access pattern fits a key-value/document model; not a drop-in for relational workloads

In an interview, name all three and the pinning gotcha on RDS Proxy. The “right” answer is usually RDS Proxy, with reserved concurrency as a stopgap and DynamoDB as the answer if the data model allows it.

6. IAM patterns for containerized + serverless apps

How code gets AWS permissions without long-lived keys:

Workload Pattern Mechanism
Lambda execution role role attached to the function; SDK picks it up automatically
ECS task task role (app identity) + execution role (ECS infra: pull image, fetch secrets, write logs) two distinct roles — common confusion point
EC2 instance profile role attached to the instance; SDK reads it from instance metadata
EKS pod IRSA or EKS Pod Identity ServiceAccount annotated with a role ARN; SDK assumes it via OIDC. Pod Identity (newer) is simpler — no OIDC provider per cluster
CI/CD (GitHub Actions) GitHub OIDC federation GitHub’s OIDC token is exchanged for AWS temp creds via an IAM role with a trust policy scoped to the repo; no long-lived AWS_ACCESS_KEY_ID secret
Cross-account assume-role role in account B with a trust policy allowing a principal in account A; sts:AssumeRole

The unifying principle: every workload gets a role, not keys. Keys (AWS_ACCESS_KEY_ID/SECRET) are a smell — they don’t rotate, they leak, they can’t be scoped per-request. See Security_Identity_and_Compliance/02_.../02_advanced_role_patterns.md.

7. Cost gotchas reference

The bills that surprise teams:

Gotcha Why it bites Mitigation
NAT Gateway data processing ~$0.045/GB processed on top of the hourly charge — a Lambda pushing TBs to S3 from a private subnet pays this VPC gateway endpoint for S3/DynamoDB (free); interface endpoints for other services
CloudWatch Logs ingestion ~$0.50/GB ingested — verbose logging dominates the CloudWatch bill, not storage log at INFO not DEBUG in prod; sample; set retention; ship high-volume logs elsewhere
CloudWatch custom metric cardinality each unique dimension combination is a separate metric (~$0.30/metric/mo) — user_id as a dimension = millions of metrics never put high-cardinality values in dimensions; use EMF + Logs Insights for that
Cross-AZ traffic ~$0.01/GB each direction between AZs — chatty multi-AZ services pay constantly AZ-aware routing; co-locate chatty components; it’s the price of HA
S3 request costs at scale GET/PUT are cheap individually but millions of small-object requests add up; LIST is pricier batch; use larger objects; CloudFront in front; S3 Inventory instead of LIST
KMS per-request charges ~$0.03 per 10k requests — a hot path calling Decrypt per request adds up use envelope encryption (decrypt the data key once, cache it); KMS data key caching
Idle NAT Gateway / NLB hourly ~$32/mo each just for existing, before any traffic delete unused ones; consolidate; question whether you need a NAT GW per AZ in dev
Provisioned concurrency / idle Aurora pay 24/7 even at zero traffic only for latency-critical paths; Aurora Serverless v2 scales down but not to zero

8. boto3 idioms

Python-specific patterns interviewers expect:

import boto3
from botocore.config import Config

# Client/session reuse — create once, not per request.
# In Lambda: module-level, outside the handler.
session = boto3.Session()
config = Config(
    retries={"max_attempts": 5, "mode": "adaptive"},  # adaptive = client-side rate limiting
    connect_timeout=3,
    read_timeout=10,
)
s3 = session.client("s3", config=config)

# Paginators — never assume one response has everything
paginator = s3.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket="my-bucket", Prefix="logs/"):
    for obj in page.get("Contents", []):
        process(obj)

# Streaming — don't load whole objects into memory
obj = s3.get_object(Bucket="b", Key="big.json")
for line in obj["Body"].iter_lines():
    process(line)

s3.upload_fileobj(file_like, "b", "key")      # streams the upload
s3.download_fileobj("b", "key", file_like)    # streams the download

# Async — for async apps, use aioboto3 / aiobotocore
# import aioboto3
# async with aioboto3.Session().client("s3") as s3:
#     await s3.put_object(...)

Key points:

  • Reuse clients/sessions — client creation is expensive (loads service models). In Lambda, module-level.
  • retries.mode="adaptive" — client-side rate limiting that backs off when AWS throttles; better than the default “legacy” mode.
  • Always paginatelist_* APIs cap results; a paginator handles continuation tokens for you.
  • Stream large payloadsupload_fileobj/download_fileobj/iter_lines instead of reading whole objects.
  • Set timeouts via botocore.config.Config — defaults are generous; a hung AWS call shouldn’t hang your request.
  • Asyncaioboto3 (wraps aiobotocore) for asyncio apps; don’t call sync boto3 in an async handler without run_in_executor.

Interview angle

  • “SNS vs SQS vs EventBridge?” — SQS = queue/work distribution; SNS = pub/sub fan-out; EventBridge = routing many event types to many targets with filtering, archive/replay, and SaaS integration. Add Kinesis when you need ordered high-throughput streaming with replay.
  • “Lambda vs Fargate vs EKS?” — Lambda for event-driven/spiky/short; Fargate for long-running services without node management; EKS when you’re already K8s or need the ecosystem. Cost and ops burden rise left to right.
  • “How do you connect Lambda to RDS at scale?” — RDS Proxy (watch pinning), reserved concurrency as a cap, or DynamoDB if the data model fits. The connection-storm problem is the thing being tested.
  • “How does your container/CI get AWS permissions?” — roles, never keys: task role for ECS, IRSA/Pod Identity for EKS, GitHub OIDC for CI. Long-lived access keys are a red flag.
  • “What’s a surprising AWS bill?” — NAT Gateway data processing, CloudWatch Logs ingestion, custom-metric cardinality, cross-AZ traffic. Knowing these signals real operational experience.
  • “boto3 best practices?” — reuse clients, paginate everything, adaptive retries, stream large objects, set timeouts via Config, aioboto3 for async.