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 paginate —
list_*APIs cap results; a paginator handles continuation tokens for you. - Stream large payloads —
upload_fileobj/download_fileobj/iter_linesinstead of reading whole objects. - Set timeouts via
botocore.config.Config— defaults are generous; a hung AWS call shouldn’t hang your request. - Async —
aioboto3(wrapsaiobotocore) for asyncio apps; don’t call sync boto3 in an async handler withoutrun_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,aioboto3for async.