AWS CloudFront Caching and Invalidation
Control CloudFront TTLs, invalidate or version content, and use an origin shield to protect the origin.
The AWS services interviewers ask about: compute, storage, IAM, networking, databases, serverless, messaging and well-architected design.
Official reference: AWS documentation
Control CloudFront TTLs, invalidate or version content, and use an origin shield to protect the origin.
Read by key with Query, avoid full-table Scans, and add indexes to serve the access patterns you actually have.
Compare ECS and EKS for running containers on AWS, and choose by control, portability and team capability.
Match events and run schedules with EventBridge, route to targets, and handle delivery failures.
Assume roles with STS, separate trust from permission policies, and use temporary credentials instead of long-lived keys.
Reduce cold-start latency with smaller packages, provisioned concurrency and initialization outside the handler.
Use Multi-AZ for failover availability and read replicas for read scaling, and stop confusing the two.
Move objects to cheaper storage classes and expire them automatically with S3 lifecycle rules.
Store secrets centrally, rotate credentials automatically with a two-user pattern, and fetch them at runtime.
Why API Gateway errors and your Lambda errors look different to clients, and how to give every failure one status, shape and request ID.
How EC2 Auto Scaling reacts to load, why scaling on the wrong metric backfires, and how to cap a fleet at what its databases and APIs can take.
How CloudWatch statistics, periods and M-of-N alarms decide when you get paged, what missing data does, and which signals deserve a page.
Seeing where AWS money goes with tags and Cost Explorer, why budgets alert but don't stop spend, and how to fence experiments with SCPs.
How to design DynamoDB partition and sort keys from your queries: listing access patterns first, composite sort keys, GSIs and hot partitions.
How DynamoDB condition expressions stop lost updates: create-only puts, version checks, atomic counters with guards, and transactions.
How IAM decides allow or deny: explicit denies, SCPs, permissions boundaries, resource policies, and how to debug an AccessDenied error.
What happens inside a Lambda execution environment from init to shutdown, what survives between calls, and how timeouts and concurrency add up.
Who retries a failed Lambda invocation (the caller, the async queue or the event source) and how to write handlers that survive running twice.
How a NAT gateway gives private subnets outbound internet access, why you want one per Availability Zone, and where its data charges come from.
Why RDS runs out of connections long before it runs out of CPU, how to size pools from query rate and latency, and when RDS Proxy actually helps.
What an IAM role is, how AssumeRole hands out short-lived keys, how EC2, Lambda and ECS get them automatically, and why that beats access keys.
What S3 guarantees after a write, why concurrent writers still overwrite each other, and how bucket policies and presigned URLs grant access.
How S3 versioning keeps old copies and delete markers, how to restore an overwritten or deleted file, and what versioning can't protect against.
Security groups are stateful and sit on interfaces; network ACLs are stateless, ordered and sit on subnets. What that means for reply traffic.
What AWS secures and what you secure, how the line moves across EC2, RDS, Lambda and S3, and who owns the fix when something goes wrong.
How SNS fans one event out to many subscribers, why each subscription succeeds or fails alone, and when to put an SQS queue behind each one.
Why SQS delivers messages more than once, how the visibility timeout and receipt handles work, and how to process slow jobs without duplicates.
How VPC route tables pick a target by longest prefix, what makes a subnet public or private, and how to debug an instance that can't connect.
Reasoning about AWS design trade-offs: which failures you plan for, how availability multiplies across dependencies, and what DR tiers cost.
A Region is a separate geographic area, like us-east-1 or eu-west-1. Regions are isolated from each other, most services are Regional, and your data stays in a Region unless you move or replicate it, which matters for latency, compliance and data residency.
An Availability Zone is one or more discrete data centers inside a Region, with independent power, cooling and networking, linked to the other AZs by low-latency connections. Every Region has several AZs, and spreading resources across at least two of them is the basic high-availability pattern: if one AZ fails, the others keep serving.
Edge locations are points of presence in many more cities than there are Regions. CloudFront, Route 53 and AWS Global Accelerator use them to cache content, answer DNS queries and terminate connections close to users.
I'd also mention Local Zones and Wavelength Zones, which push compute closer to specific metro areas or 5G networks.
Likely follow-up: How do you choose which Region to deploy a new workload to? · Is us-east-1a the same physical AZ in every AWS account?
A common strategy: a Savings Plan for the steady baseline, On-Demand for the variable part, and Spot for interruptible capacity.
Likely follow-up: What kinds of workloads should never run on Spot? · How do you get guaranteed capacity without a 1- or 3-year term?
Effect, Action, Resource and an optional Condition. Identity-based policies attach to users, groups or roles; resource-based policies, like bucket policies, attach to a resource and name a Principal.Current best practice: people sign in through IAM Identity Center or federation, workloads use roles, IAM users with access keys are the exception, and the root user is locked away with MFA.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::app-uploads/*"
}
]
}Likely follow-up: What is the difference between an AWS managed policy, a customer managed policy and an inline policy?
Security groups can use another security group as the source, which is how you say "only the load balancer may reach the app tier". I use security groups as the main control and NACLs as a coarse subnet guardrail, for example to block a known bad CIDR range.
# App tier accepts port 8080 only from the ALB's security group
aws ec2 authorize-security-group-ingress \
--group-id sg-0app1234example \
--protocol tcp --port 8080 \
--source-group sg-0alb5678exampleLikely follow-up: A NACL allows inbound 443 and outbound 443 only. Why do HTTPS clients time out?
Apart from the One Zone classes, data is stored across multiple AZs. Lifecycle rules move objects down the tiers as they age.
Likely follow-up: When is Intelligent-Tiering not a good fit?
A subnet is public when its route table sends 0.0.0.0/0 (and ::/0 for IPv6) to an internet gateway. Instances there also need a public IPv4 or Elastic IP address to be reachable. A private subnet has no route to the internet gateway.
Private instances that need outbound access, for patches or third-party APIs, get a route to a NAT gateway. The NAT gateway translates their private addresses to its own public IP and only allows connections initiated from inside, so nothing on the internet can open a connection to them. For IPv6 the equivalent is an egress-only internet gateway.
A classic (zonal) NAT gateway sits in a public subnet of one AZ, so for high availability you deploy one per AZ and point each private route table at the NAT gateway in its own AZ. AWS has also added a regional NAT gateway mode that spans AZs automatically. If the traffic is only to services like S3 or DynamoDB, VPC endpoints avoid NAT charges entirely.
PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PrivateRouteA:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTableA
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGatewayALikely follow-up: Why is a single NAT gateway a risk for a multi-AZ application? · What is the difference between a NAT gateway and a NAT instance?
The Classic Load Balancer is legacy; I wouldn't use it for new designs.
Likely follow-up: A partner needs to allowlist fixed IP addresses for your HTTP API behind an ALB. What do you do?
They solve different problems: Multi-AZ is for availability, read replicas are for read scaling.
Many production setups use both: Multi-AZ for the primary, plus read replicas for reporting or read-heavy traffic.
Likely follow-up: How does the application find the new primary after a failover? · How does this differ in Aurora?
I'd spread every tier across at least two, ideally three, Availability Zones in one Region.
Then I'd say what it survives (an instance or AZ failure) and what it doesn't (a Regional outage), and propose a DR strategy if the RTO requires one.
Likely follow-up: How would you deploy new versions of this app with zero downtime? · What would you change to survive the loss of a whole Region?
A launch template is a versioned definition of how to launch an instance: AMI, instance type, key pair, security groups, IAM instance profile, user data, volumes and tags. It replaces the older launch configurations, which AWS has deprecated.
An Auto Scaling group uses a launch template to keep a fleet between a minimum and maximum size, aiming at a desired capacity. It spreads instances across the subnets (AZs) you give it, replaces unhealthy instances and registers new ones with load balancer target groups.
Scaling options:
Use the ELB health check type so instances that are running but failing the app's health check get replaced. Also worth knowing: lifecycle hooks, instance refresh for rolling AMI updates, warm pools, and mixed instances policies that combine On-Demand and Spot.
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name web-asg \
--launch-template 'LaunchTemplateName=web-lt,Version=$Latest' \
--min-size 2 --max-size 6 --desired-capacity 2 \
--vpc-zone-identifier "subnet-0aaa1111example,subnet-0bbb2222example" \
--target-group-arns arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web/0123456789abcdef \
--health-check-type ELB --health-check-grace-period 300Likely follow-up: How do you let in-flight requests finish before an instance is terminated during scale-in?
EBS is network-attached block storage. A volume lives in one AZ, persists independently of the instance, can be detached and reattached, and can be backed up with snapshots, which are incremental, stored by AWS in S3 and copyable across Regions. Volume types include general purpose SSD (gp3, gp2), provisioned IOPS SSD (io2, io1) and HDD types for throughput or cold data (st1, sc1). With gp3 you set IOPS and throughput independently of the volume size.
Instance store is disk physically attached to the host. It's very fast and included in the instance price, but it's ephemeral: data survives a reboot but is lost when the instance stops, hibernates or terminates, or if the underlying hardware fails. Only some instance types have it.
So EBS for root volumes, self-managed databases and anything that must persist; instance store for caches, scratch space, buffers, or data that is already replicated elsewhere, such as nodes of a distributed database.
Likely follow-up: How would you move an EBS volume to a different Availability Zone?
For Windows file shares, high-performance computing or NetApp compatibility there's the FSx family, such as FSx for Windows File Server, FSx for Lustre and FSx for NetApp ONTAP.
Rule of thumb: work queue, SQS; fan-out, SNS; routing events between services, EventBridge; ordered, replayable streams, Kinesis.
Likely follow-up: Where does Amazon MSK fit into this comparison? · How would you guarantee ordering end to end?
So: relational data with flexible queries, RDS or Aurora; massive scale with well-known key-based access, DynamoDB.
Likely follow-up: What would make you migrate from RDS for PostgreSQL to Aurora PostgreSQL?
When no idle execution environment is available, Lambda must create one: load your code, start the runtime and run the initialization code outside your handler. That Init phase adds latency to that request, which is the cold start. The environment is then reused for later invocations until Lambda recycles it.
Ways to reduce it:
Cold starts usually hit a small share of requests, so I'd measure first, using the Init Duration in the logs or traces.
import os
import boto3
# Init phase: runs once per execution environment, reused while warm
table = boto3.resource("dynamodb").Table(os.environ["TABLE_NAME"])
def handler(event, context):
# Runs on every invocation
item = table.get_item(Key={"pk": event["id"]}).get("Item")
return {"statusCode": 200 if item else 404}Likely follow-up: Why can SnapStart cause problems for code that generates unique IDs or random seeds during init?
The pillars trade off against each other; multi-Region reliability, for instance, costs more. The AWS Well-Architected Tool lets you review a workload against each pillar's questions, and lenses extend it for areas like serverless.
Likely follow-up: Which pillar would you prioritize for an early-stage startup, and why?
AssumeRole work?midA role has two kinds of policy:
lambda.amazonaws.com, an account, a specific role, or a federated identity provider.A trusted principal calls sts:AssumeRole (for cross-account access its own identity policy must also allow that action) and gets temporary credentials: an access key ID, a secret access key and a session token. They last for the session duration, one hour by default and configurable up to the role's maximum, then expire on their own, so there's nothing long-lived to leak or rotate.
Services use this behind the scenes: EC2 instance profiles, Lambda execution roles and ECS task roles all give the workload temporary credentials that the SDK refreshes automatically. Roles are also the standard mechanism for cross-account access, and AssumeRoleWithWebIdentity and AssumeRoleWithSAML cover federation.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}Likely follow-up: What is role chaining, and how does it limit the session duration?
RPO (recovery point objective) is how much data you can afford to lose, measured in time; RTO (recovery time objective) is how long you can be down. AWS's DR whitepaper describes four strategies, from cheapest to most expensive:
Tools include AWS Backup, S3 replication, Aurora Global Database, DynamoDB global tables and Route 53 health checks. Two strong points: fail over with data plane operations, and remember replication also copies corruption, so keep point-in-time backups and test the plan.
Likely follow-up: How would you switch traffic to the DR Region in a pilot light setup? · Why does the whitepaper recommend relying on data plane rather than control plane operations during failover?
Buckets are private by default. Within one account an allow in either the IAM policy or the bucket policy is enough, and an explicit deny anywhere wins. Access points help with shared datasets, and IAM Access Analyzer flags buckets shared publicly or with other accounts.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::app-data", "arn:aws:s3:::app-data/*"],
"Condition": { "Bool": { "aws:SecureTransport": "false" } }
}
]
}Likely follow-up: How would you let another account upload objects while your account keeps ownership of them?
Versioning keeps every version of an object. An overwrite creates a new version, and a delete without a version ID only adds a delete marker, so the previous version can be restored. It protects against accidental deletes and overwrites, and replication requires it. Once enabled it can be suspended but never fully turned off. MFA Delete and Object Lock add stronger protection.
Lifecycle rules act on objects by age, scoped by prefix or tags:
Together: version the bucket for protection, and let lifecycle rules age data into cheaper tiers and trim old versions.
{
"Rules": [
{
"ID": "logs-tiering",
"Filter": { "Prefix": "logs/" }, "Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" }
],
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
}
]
}Likely follow-up: A user deleted an object in a versioned bucket. How do you get it back?
A classic gotcha: EC2 doesn't report memory or disk usage out of the box; you need the CloudWatch agent.
aws cloudwatch put-metric-alarm \
--alarm-name web-high-cpu \
--namespace AWS/EC2 --metric-name CPUUtilization \
--dimensions Name=AutoScalingGroupName,Value=web-asg \
--statistic Average --period 300 --evaluation-periods 2 \
--threshold 80 --comparison-operator GreaterThanThreshold \
--alarm-actions arn:aws:sns:us-east-1:111122223333:ops-alertsLikely follow-up: Someone deleted a production security group rule. How do you find out who did it?
Every item is identified by its primary key:
GetItem needs the exact value.Query can fetch a range with begins_with, between or comparisons, in order.Choosing keys starts from access patterns, not entities. The partition key should have high cardinality and spread traffic evenly, like userId or orderId, not status or today's date. The sort key models the one-to-many relationship or ordering you need, for example ORDER#2024-05-01#123 so a customer's orders come back sorted by date.
In single-table design, generic key names like PK and SK with prefixed values let one table hold several entity types. Patterns the table's key can't serve go to a GSI; a Scan should be the exception.
Likely follow-up: How would you model customers and their orders so you can list a customer's recent orders?
.fifo, every message carries a MessageGroupId, and a message sent again within the 5-minute deduplication interval is dropped, based on an explicit MessageDeduplicationId or content-based deduplication (a SHA-256 hash of the body). Throughput is lower than standard, though batching and high-throughput mode raise it a lot.Ordering is per message group, not global, so the group ID is your parallelism knob: different groups, say different customers, are processed in parallel, while messages within one group are strictly sequential. While a message in a group is in flight, the next message in that group isn't delivered.
Use FIFO when order or duplicates really matter, such as commands against one account; otherwise use standard with idempotent consumers.
Likely follow-up: Does exactly-once processing in FIFO mean your consumer can skip idempotency?
When a consumer receives a message, SQS doesn't remove it; it hides it for the visibility timeout (30 seconds by default, up to 12 hours). The consumer must call DeleteMessage before it expires. If the consumer crashes or is too slow, the message reappears and another consumer gets it, which is how SQS avoids losing messages and also why duplicates happen.
So set the timeout above your processing time, extend it with ChangeMessageVisibility as a heartbeat for long jobs, and when Lambda consumes the queue set the visibility timeout to at least six times the function timeout, as AWS recommends.
A dead-letter queue catches poison messages. The source queue's redrive policy sets maxReceiveCount: once a message has been received that many times without being deleted, SQS moves it to the DLQ. Alarm on the DLQ's depth, inspect the messages, fix the bug, then redrive them back to the source queue. A FIFO queue needs a FIFO DLQ.
import boto3
sqs = boto3.client("sqs")
QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/111122223333/orders"
while True:
# Long polling: wait up to 20 s for messages instead of returning empty
resp = sqs.receive_message(QueueUrl=QUEUE_URL, MaxNumberOfMessages=10, WaitTimeSeconds=20)
for msg in resp.get("Messages", []):
process(msg["Body"]) # must finish within the visibility timeout
sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=msg["ReceiptHandle"])Likely follow-up: Your Lambda consumer times out after 60 seconds and messages are processed twice. What would you check?
Never put access keys in the AMI, the code or config files. Create an IAM role that trusts ec2.amazonaws.com, give it least-privilege permissions (say s3:GetObject on one bucket prefix), and attach it to the instance through an instance profile, the container that passes a role to EC2.
The instance metadata service (IMDS) then serves temporary credentials for that role. The AWS SDKs and CLI find them automatically through the default credential provider chain and refresh them before they expire, so your code just creates an S3 client without any keys.
Worth adding: require IMDSv2, which needs a session token obtained with a PUT request and helps protect against SSRF attacks that try to steal credentials from the metadata endpoint. The same idea applies elsewhere: Lambda execution roles, ECS task roles, and EKS Pod Identity or IAM roles for service accounts on Kubernetes.
# IMDSv2: get a session token, then list the attached role's credentials
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/Likely follow-up: How does a pod running on EKS get its own AWS permissions?
Everything starts as an implicit deny: nothing is allowed unless a policy allows it. Then:
For cross-account requests both sides must allow: the caller's identity policy and the resource's policy (or the trust policy of a role to assume).
In the snippet, every object action on reports/* is allowed except DeleteObject, because the explicit deny wins.
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": "s3:*", "Resource": "arn:aws:s3:::reports/*" },
{ "Effect": "Deny", "Action": "s3:DeleteObject", "Resource": "arn:aws:s3:::reports/*" }
]
}Likely follow-up: Do SCPs restrict users and roles in the organization's management account?
At rest, S3 encrypts every new object by default with SSE-S3, using keys S3 manages. The other options:
In transit, use HTTPS and enforce it with a bucket policy that denies requests where aws:SecureTransport is false.
SSE-KMS with a customer managed key is the usual pick for separation of duties: someone with s3:GetObject but no kms:Decrypt on the key still can't read the data.
aws s3api put-bucket-encryption --bucket app-data \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "alias/app-data"
},
"BucketKeyEnabled": true
}]
}'Likely follow-up: A role has s3:GetObject on a bucket but gets Access Denied on objects. What would you check?
A presigned URL grants time-limited access to one object operation, such as a GET to download or a PUT to upload, to someone who has no AWS credentials. Your backend signs the URL with its own credentials, and S3 checks the signature, the expiry and the signer's permissions when the request arrives.
Typical uses:
PUT URL, so the file goes straight to S3 instead of streaming through your servers or API Gateway.Things to know: the URL can only do what the signer is allowed to do. With Signature Version 4 the maximum lifetime is 7 days, and a URL signed with temporary credentials, such as a role's, stops working when those credentials expire. Anyone holding the URL can use it until then, so keep expiries short. For content behind CloudFront, use CloudFront signed URLs or signed cookies instead.
import boto3
s3 = boto3.client("s3")
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "invoices-prod", "Key": "2024/inv-123.pdf"},
ExpiresIn=900, # seconds
)Likely follow-up: How would you restrict the maximum size of a file users upload directly to S3?
CloudFront is AWS's content delivery network. Users are routed to a nearby edge location; if the object is cached there it's served immediately, otherwise CloudFront fetches it from the origin (an S3 bucket, an ALB, API Gateway or any HTTP server) over the AWS network, often through regional edge caches, and caches it.
Performance: lower latency, less load on the origin, TLS terminated close to the user, compression, HTTP/2 and HTTP/3. Cache behaviors map path patterns to origins, each with a cache policy that sets TTLs and which headers, cookies and query strings form the cache key. Stale content is cleared with invalidations or, better, versioned file names.
Security: AWS Shield Standard against common DDoS attacks, AWS WAF, HTTPS with ACM certificates, geographic restrictions, signed URLs or cookies for private content, and origin access control so an S3 origin only accepts requests from your distribution.
For logic at the edge, CloudFront Functions handle lightweight header and URL rewrites, and Lambda@Edge handles heavier work.
Likely follow-up: How do you release a new version of a single-page app without users getting stale files?
Most policies combine with health checks, and alias records point to AWS resources such as ALBs or CloudFront, even at the zone apex. Remember TTLs: failover only takes effect as clients' DNS caches expire.
Likely follow-up: Why might some users keep reaching the failed Region for a while after a DNS failover?
VPC peering is a direct private connection between two VPCs, in the same or different accounts and Regions. There's no extra hop or bandwidth bottleneck, and you pay only for data transfer. But it's not transitive: if A peers with B and B peers with C, A can't reach C through B. CIDR ranges can't overlap, and with many VPCs you end up managing a full mesh of connections and route entries.
Transit Gateway is a Regional hub-and-spoke router. Each VPC, VPN connection or Direct Connect gateway attaches once, and transit gateway route tables decide who can reach whom, so you can segment networks: prod can't reach dev, but everyone reaches shared services. Transit gateways in different Regions can peer with each other. The trade-off is cost: hourly charges per attachment plus per-GB data processing.
So peering for a handful of VPCs or heavy point-to-point traffic, Transit Gateway once you have many VPCs or hybrid connectivity. To expose just one service rather than a whole network, use PrivateLink.
Likely follow-up: Two VPCs you need to connect have overlapping CIDR ranges. What are your options?
My default is ECS on Fargate for a team that wants containers on AWS with minimal operations; EKS when the team already runs Kubernetes or needs its ecosystem; EC2 capacity for either when you need GPUs, specific instance types, or the lowest cost at steady scale, often with Spot.
Likely follow-up: What trade-offs come with running containers on Fargate instead of EC2?
The default quotas are 20 GSIs and 5 LSIs per table. In practice I almost always use GSIs, and pick an LSI only for a strongly consistent alternate sort order known up front. Watch out: in provisioned mode, an under-provisioned GSI can throttle writes to the base table.
OrdersTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- { AttributeName: PK, AttributeType: S }
- { AttributeName: SK, AttributeType: S }
- { AttributeName: email, AttributeType: S }
KeySchema: [{ AttributeName: PK, KeyType: HASH }, { AttributeName: SK, KeyType: RANGE }]
GlobalSecondaryIndexes:
- IndexName: by-email
KeySchema: [{ AttributeName: email, KeyType: HASH }]
Projection: { ProjectionType: KEYS_ONLY }Least privilege means each identity gets only the permissions it needs for its task, on only the resources it needs, for only as long as it needs them. It limits the blast radius of a bug, a leaked credential or a compromised workload.
In practice:
dynamodb:GetItem on one table ARN instead of dynamodb:* on *. Add conditions such as source VPC endpoint, tags or MFA.Review regularly, because permissions tend to accumulate.
Both store values encrypted with KMS, are controlled with IAM, and can be read at runtime by Lambda, ECS, CloudFormation and your own code.
Secrets Manager is built for secrets:
Parameter Store is a general configuration store:
/app/prod/db-url, String, StringList and encrypted SecureString types, with version history.My rule: database passwords, API keys and anything that should rotate go in Secrets Manager; endpoints, feature flags and other configuration go in Parameter Store. Either way, cache values in the application instead of calling the API on every request.
Likely follow-up: How would you rotate a database password without breaking running applications?
cdk synth produces a CloudFormation template, so deployment, state and rollback are still CloudFormation's.terraform plan shows the diff before apply. You manage the state file yourself, typically in S3 with locking, and protect it because it can contain secrets. OpenTofu is an open-source fork.I'd pick CDK or CloudFormation for AWS-only teams that want managed state, and Terraform for multi-cloud setups or where it's already the organization's standard.
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
Env:
Type: String
AllowedValues: [dev, prod]
Resources:
AssetsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'acme-assets-${Env}'
VersioningConfiguration:
Status: Enabled
Outputs:
BucketArn: { Value: !GetAtt AssetsBucket.Arn }Likely follow-up: Someone changed a resource by hand in the console. How do you detect and fix the drift?
API Gateway is a managed front door for APIs: routing, authorization, throttling, CORS, custom domains, stages and logging, with integrations to Lambda, HTTP backends, other AWS services, and private resources in a VPC through VPC links.
$connect and $disconnect invoke your backend, which pushes messages back through a connection callback URL.Authorization options include IAM, Lambda authorizers and Cognito. Two limits to know: REST API integrations time out after 29 seconds by default (it can be raised for Regional and private APIs), and payloads are capped at 10 MB, so large files go straight to S3 with presigned URLs.
Likely follow-up: How would you protect a public API from one client consuming all the capacity?
Concurrency is the number of requests a function handles at the same moment, roughly requests per second times average duration. Each concurrent request needs its own execution environment.
429 error, while asynchronous and event source invocations are retried. Lambda also limits how fast each function can scale out.For SQS event sources, the event source mapping's maximum concurrency setting is a gentler way to cap consumers than reserved concurrency.
# Guarantee and cap this function at 100 concurrent executions
aws lambda put-function-concurrency --function-name orders-api \
--reserved-concurrent-executions 100
# Keep 20 environments initialized on the "live" alias
aws lambda put-provisioned-concurrency-config --function-name orders-api \
--qualifier live --provisioned-concurrent-executions 20Likely follow-up: A Lambda function overwhelms an RDS database during traffic spikes. What would you change?
VPC endpoints let resources in private subnets reach AWS services without an internet gateway or NAT gateway, keeping traffic on the AWS network.
PrivateLink also exposes your own service: put it behind an NLB, create an endpoint service, and consumers in other VPCs or accounts create interface endpoints to it. Only that service is shared, overlapping CIDRs don't matter, and no peering is needed.
Endpoints also cut NAT data processing costs, and bucket policies can require a specific endpoint with aws:SourceVpce.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "OnlyThroughOurVpcEndpoint",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::internal-data", "arn:aws:s3:::internal-data/*"],
"Condition": { "StringNotEquals": { "aws:SourceVpce": "vpce-0abc1234example" } }
}
]
}Likely follow-up: Your NAT gateway bill is high and most traffic goes to S3. What would you change?
KMS manages encryption keys in hardware security modules, and a KMS key's material never leaves KMS unencrypted. Every key has a key policy; IAM policies and grants add to it, and every use is logged in CloudTrail. Keys are AWS owned (used inside services), AWS managed (created per service in your account, like aws/s3), or customer managed, where you control the policy, rotation and deletion.
Encrypt only accepts small payloads (up to 4 KB), and sending all your data to KMS would be slow anyway. So services use envelope encryption:
GenerateDataKey returns a plaintext data key plus the same key encrypted under your KMS key.Decrypt and decrypt the data locally.S3, EBS and RDS encryption work this way. You get speed, no size limit, and access to the data hinges on permission to decrypt one small key.
import boto3
kms = boto3.client("kms")
resp = kms.generate_data_key(KeyId="alias/app-data", KeySpec="AES_256")
plaintext_key = resp["Plaintext"] # use locally, never store it
encrypted_key = resp["CiphertextBlob"] # store next to the ciphertext
# ... encrypt the payload locally with plaintext_key (for example AES-GCM) ...
# Later: recover the data key in order to decrypt
plaintext_key = kms.decrypt(CiphertextBlob=encrypted_key)["Plaintext"]Likely follow-up: What happens to data encrypted under a KMS key that is scheduled for deletion? · Does rotating a KMS key re-encrypt existing data?
Visibility first, then the biggest levers.
gp2 to gp3 migrations, sensible CloudWatch Logs retention.Trusted Advisor gives quick wins. Then make it continuous with tagging policies, per-team budgets and regular reviews.
Likely follow-up: Which hidden data transfer costs have you seen surprise teams on AWS?
PUT URL, then uploads straight to an uploads bucket. No file bytes pass through API Gateway or Lambda.processed bucket (never the source bucket, which would trigger itself in a loop) and records metadata and status in DynamoDB.If the pipeline grows into several steps, like moderation and notifications, I'd orchestrate them with Step Functions. The design scales to zero when idle.
Likely follow-up: How would you tell the client that processing has finished?
Capacity units: one RCU is one strongly consistent read per second of an item up to 4 KB, or two eventually consistent reads; one WCU is one write per second of up to 1 KB. Transactional requests consume twice as much.
Either mode can still throttle if traffic concentrates on one partition key, because each partition has its own throughput ceiling. Switching modes is allowed only a limited number of times.
DynamoDB spreads items across partitions by hashing the partition key, and each partition has a ceiling of 3,000 read units and 1,000 write units per second. A hot partition appears when a disproportionate share of traffic hits one key or a few: a celebrity's profile, today's date as the key, a global counter, a status attribute. You get throttled while the table has spare capacity.
DynamoDB helps: burst capacity absorbs short spikes, and adaptive capacity shifts throughput to busy partitions and can split partitions that stay hot. But a single hot item can never exceed the per-partition limit.
Fixes:
userId instead of country.2024-05-01#7 out of 10 shards, to spread writes, and query all shards in parallel when reading.CloudWatch Contributor Insights shows the most accessed keys.
Likely follow-up: How would you implement a like counter for a post that goes viral?
ElastiCache is a managed in-memory data store for sub-millisecond reads. Use it to offload repeated database queries, store sessions, build leaderboards with sorted sets, rate limit, or do pub/sub. It supports Valkey, Redis OSS and Memcached, either as a serverless cache or as node-based clusters.
Strategies:
Also plan invalidation on writes, protection against a stampede when a hot key expires, and the eviction policy for when memory fills up.
Step Functions orchestrates workflows as state machines written in Amazon States Language. States include Task (call Lambda or an AWS service API directly), Choice, Parallel, Map, Wait, Succeed and Fail, and each state can declare Retry and Catch, so error handling lives in the workflow instead of your code. The .sync pattern waits for a job such as an ECS task to finish, and .waitForTaskToken pauses until a person or external system calls back.
.sync or callbacks.I reach for Step Functions when a Lambda starts calling other Lambdas and managing its own retries.
{
"StartAt": "ChargeCard",
"States": {
"ChargeCard": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "charge-card", "Payload.$": "$" },
"Retry": [{ "ErrorEquals": ["States.TaskFailed"], "MaxAttempts": 3, "BackoffRate": 2 }],
"Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "PaymentFailed" }],
"End": true
},
"PaymentFailed": { "Type": "Fail", "Error": "PaymentFailed" }
}
}AWS describes seven migration strategies, chosen per application after a portfolio assessment:
For large migrations AWS generally recommends rehost, replatform and relocate to move quickly, then modernizing once in the cloud. AWS DMS handles database migrations and DataSync moves file data.
Likely follow-up: Which strategy would you choose for a legacy Oracle database with a tight data center exit deadline?
There are three invocation models:
Invoke. Lambda doesn't retry; the caller sees the error and decides.With polling sources, errors affect the whole batch. For SQS, failed messages reappear after the visibility timeout and eventually land in the queue's DLQ. For Kinesis and DynamoDB Streams, a failing batch blocks its shard until it succeeds or the records expire, so configure maximum retries, maximum record age, bisect batch on error and an on-failure destination.
For SQS and streams, enable partial batch responses so only failed items are retried, and make handlers idempotent.
def handler(event, context):
failures = []
for record in event["Records"]:
try:
process(record["body"])
except Exception:
failures.append({"itemIdentifier": record["messageId"]})
# Requires ReportBatchItemFailures on the SQS event source mapping
return {"batchItemFailures": failures}An AMI (Amazon Machine Image) is the template an instance boots from: snapshots of the root volume and any other volumes, plus metadata like the architecture and block device mapping. AMIs are Regional; you can copy them to other Regions and share them with other accounts. You can start from AWS images like Amazon Linux, Marketplace images, or your own golden AMIs built with EC2 Image Builder or Packer.
User data is a script or cloud-init configuration passed at launch. By default it runs once, as root, on the instance's first boot, and is typically used to install packages, fetch configuration or register the instance somewhere. It's limited to 16 KB and readable through the instance metadata service, so never put secrets in it.
The trade-off: baking software into the AMI gives fast, consistent boots, which matters for Auto Scaling; heavy user data keeps images generic but slows every launch and can fail at boot. A common middle ground is a golden AMI with the OS and dependencies plus a short user data script for environment-specific settings.
#!/bin/bash
# Runs as root on first boot (Amazon Linux 2023)
dnf install -y nginx
systemctl enable --now nginx
echo "Deployed at $(date)" > /usr/share/nginx/html/index.htmlA name like m7g.xlarge reads as family (m), generation (7), optional attributes (g for AWS Graviton, a for AMD, i for Intel, d for local NVMe instance storage, n for enhanced networking), then size (large, xlarge, 2xlarge and so on, with resources roughly doubling at each step).
Main families:
m, t): balanced CPU and memory for web and application servers. t instances are burstable: they earn CPU credits while idle and spend them in bursts, which suits low, spiky loads, but watch for credit exhaustion.c): more CPU per GB of memory, for batch jobs, encoding and busy web servers.r, x): in-memory databases, caches and large analytics.i, d): high local disk IOPS or throughput, for NoSQL databases and data warehousing.p, g, inf, trn): GPUs or AWS Inferentia and Trainium chips for ML and graphics.Newer generations usually give better price-performance, and Graviton (Arm) instances often more so. I'd benchmark and check Compute Optimizer rather than guess.
S3 provides strong read-after-write consistency for object operations in all Regions, and has since late 2020. After a successful PUT of a new object, an overwrite or a DELETE, any subsequent GET, HEAD or LIST reflects the change. A job can write an object and immediately list the prefix and see it, with no extra cost or configuration.
Nuances worth knowing:
If-None-Match help avoid overwriting.Older articles that describe eventual consistency for overwrites and deletes are out of date.
IAM Identity Center, the successor to AWS Single Sign-On, is the recommended way to give people access across an AWS Organization. Users sign in once, through the access portal or the CLI with aws sso login, and pick an account and a role.
Compared with IAM users in every account, you get one place to manage people, MFA and offboarding, consistent access across many accounts, and CloudTrail records of who assumed which role. IAM users remain for rare edge cases.
A layer is a .zip archive of libraries, a custom runtime or other files that you publish separately and attach to functions. Lambda extracts layers into /opt in the execution environment, and the managed runtimes include paths under /opt in their search paths, so your code imports them normally.
Why use them:
Limits and caveats: up to five layers per function, and their unzipped size still counts toward the function's deployment size limit. Layer versions are immutable and referenced by exact ARN, so upgrades are explicit. Layers only work with .zip functions; with container images you build dependencies into the image. AWS also advises against layers for Go and Rust, where dependencies belong in the compiled binary.
I'd follow the packet's path from the instance outward:
0.0.0.0/0 to a NAT gateway? A common mistake is the subnet silently using the main route table.0.0.0.0/0 to the internet gateway, with an Elastic IP?Tools: VPC Reachability Analyzer to test the path, VPC Flow Logs to see rejected traffic, and Session Manager for a shell without SSH. If the instance only needs AWS APIs like S3, a VPC endpoint may be the better fix.
There are two main patterns, and in both each account must allow the access.
sts:AssumeRole on that role's ARN. The application calls AssumeRole, receives temporary credentials and acts as B's role. It works for any service and keeps B in control.Principal, and A's role also needs an identity policy allowing those actions. The caller keeps its own identity, with no role switching. For KMS-encrypted data, the key policy must allow A too.When the other party is a third party, add an sts:ExternalId condition, as in the snippet, to prevent the confused deputy problem. Within your own organization, conditions like aws:PrincipalOrgID keep access inside it.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111111111111:role/partner-reporting" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "b7f3c2e9-example" } }
}
]
}Each Lambda execution environment opens its own database connection. When traffic spikes, Lambda scales out to hundreds or thousands of concurrent environments, each holding a connection, while a relational database supports a finite number of connections tied to its size. Idle environments also keep their connections open until they're recycled.
Fixes, usually combined:
I'd confirm the diagnosis first with the database's DatabaseConnections metric in CloudWatch.
s3:GetObject only to the cloudfront.amazonaws.com service principal for that distribution's ARN. OAC replaces the older origin access identity and also works with SSE-KMS objects, as long as the key policy lets CloudFront decrypt.us-east-1, a Route 53 alias record, and a viewer protocol policy that redirects HTTP to HTTPS.index.html as the default root object, and custom error responses that map 403 and 404 to /index.html, or a CloudFront Function that rewrites paths.index.html on each deploy.The result: HTTPS, global caching, and a bucket nobody can read directly.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::acme-web-assets/*",
"Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/EDFDVBD6EXAMPLE" } }
}
]
}Spot capacity can be reclaimed at any time with a two-minute interruption notice, so the workload has to tolerate losing instances.
On containers, ECS capacity providers and Karpenter on EKS build these practices in.
No questions match that filter.
Prefer multiple choice? All 21 AWS MCQs with answers →