Design a VPC - The Interview Exercise
- Say requirements first, then the CIDR plan, then subnets per tier per AZ, routing, security, ingress, egress, private service access, hybrid, observability, cost and failure modes, in that order. Interviewers score the order as much as the answer.
- A strong answer: VPC
10.20.0.0/16, three AZs, a public, app and data subnet in each, one NAT gateway per AZ, security groups chained ALB to app to database, gateway endpoints for S3 and DynamoDB, and a plan for what dies when an AZ dies. - Every decision here is owned by another page; this page only strings them into a story. Follow the links when an interviewer digs into one.
- State assumptions aloud and name the trade-off you took. A design with no stated trade-off reads as memorised.
- Common weak answers: one NAT gateway "to save money", a database in a public subnet "with a strict security group", a
/24VPC, and no mention of endpoints until the NAT bill arrives.
Mental model
You are drawing the floor plan, fire doors and delivery entrance of a building before anyone moves furniture in. Where the lobby is, which corridors are staff-only, which door the trucks use, and what happens when one wing floods all have to be settled on paper. Moving a wall later is possible but expensive.
Where it breaks: a building has one set of drawings and an architect with final say. An interview design is a conversation: the interviewer changes the requirements halfway through, and your job is to adapt out loud rather than defend the first drawing.
This is a threat model plus a capacity plan, written before the Terraform. The CIDR plan is a schema you cannot migrate cheaply, the route tables are the control flow, and the security groups are the access-control layer. Getting the schema wrong costs a re-IP project, in the same way a bad primary key costs a data migration.
Where it breaks: a schema is only data. A network plan also has a blast radius and a bill: one wrong route or one missing endpoint can take traffic down or run up a per-GB charge, and neither shows in a diagram review.
How it works
The prompt: design a VPC for a three-tier web application (load balancer, application servers, database) that survives the loss of one availability zone. Draw it and give CIDRs. The walk below is the order to speak in. Each step is two or three lines plus a link to the page that owns the reasoning. AWS names are used for concreteness; the name-mapping table later covers GCP and Azure.
Step 1 - clarify requirements
Ask before drawing. Each answer changes the design:
- Load and growth. Requests per second now and in a year, and how many instances or pods that implies. It sets subnet sizes.
- Data sensitivity and compliance. Regulated data (payment, health) pushes towards no internet gateway on the data and app tiers and endpoints-only access, see the threat model.
- Availability targets. RTO and RPO, and whether one region is enough. "Survive an AZ" means three AZs and spare capacity; "survive a region" is a different design.
- Connectivity. Is there an on-premises data centre, another VPC, or a partner? Their CIDRs decide ours.
- Runtime. VMs or Kubernetes. Pods on the AWS VPC CNI each consume a VPC IP, see Kubernetes networking.
Step 2 - the CIDR plan
The VPC is 10.20.0.0/16 (65,536 addresses). The plan below is derived with Python's ipaddress module in the worked example; the arithmetic of prefixes is in IPv4 addressing and subnetting, and why the VPC block cannot be resized later is in the cloud network model.
| Tier | AZ | CIDR | Usable hosts (AWS) | Route table |
|---|---|---|---|---|
| Public (ALB, NAT) | 1 | 10.20.0.0/24 | 251 | public: 0.0.0.0/0 to IGW |
| Public (ALB, NAT) | 2 | 10.20.1.0/24 | 251 | public: 0.0.0.0/0 to IGW |
| Public (ALB, NAT) | 3 | 10.20.2.0/24 | 251 | public: 0.0.0.0/0 to IGW |
| App | 1 | 10.20.16.0/22 | 1,019 | app-1: 0.0.0.0/0 to NAT in AZ 1 |
| App | 2 | 10.20.20.0/22 | 1,019 | app-2: 0.0.0.0/0 to NAT in AZ 2 |
| App | 3 | 10.20.24.0/22 | 1,019 | app-3: 0.0.0.0/0 to NAT in AZ 3 |
| Data | 1 | 10.20.32.0/24 | 251 | data: local only |
| Data | 2 | 10.20.33.0/24 | 251 | data: local only |
| Data | 3 | 10.20.34.0/24 | 251 | data: local only |
| Endpoints | 1 | 10.20.40.0/28 | 11 | local only |
| Endpoints | 2 | 10.20.40.16/28 | 11 | local only |
| Endpoints | 3 | 10.20.40.32/28 | 11 | local only |
| TGW attachment | 1 | 10.20.41.0/28 | 11 | local plus on-prem routes |
| TGW attachment | 2 | 10.20.41.16/28 | 11 | local plus on-prem routes |
| TGW attachment | 3 | 10.20.41.32/28 | 11 | local plus on-prem routes |
Say why: public subnets are small because they hold only load-balancer nodes and NAT gateways. The app tier is the one that grows, so it gets a /22 per AZ. Data is small and stable. Gaps between tiers are deliberate headroom. In the whole plan, 10.20.128.0/17 (half the VPC) is left unallocated. Teams that run many VPCs allocate the VPC block itself from an IP address manager, such as AWS VPC IPAM, so no two VPCs overlap.
Step 3 - routing
Three route tables, matching the table above. The public table sends 0.0.0.0/0 to the internet gateway. Each app table sends 0.0.0.0/0 to the NAT gateway in the same AZ. The data table has only the implicit local route, so the database has no path to or from the internet at all. "Public subnet" is a routing fact, not a label. Detail in routing in a VPC.
Step 4 - security
Security groups as a chain: the ALB group allows 443 from the internet, the app group allows the app port from the ALB group (a group reference, not a CIDR), the database group allows 5432 from the app group. Nothing on the data tier allows 0.0.0.0/0. Leave network ACLs at their default of allow-all unless you have a stated reason, such as a subnet-wide explicit deny. Detail in security groups versus NACLs.
Step 5 - ingress
An Application Load Balancer in the three public subnets terminates TLS (certificate from ACM) and can sit behind AWS WAF. If the requirement is static IPs or raw TCP, use an NLB with one Elastic IP per zone. The choice is the subject of cloud load balancers; where the balancer sits in the whole request is in the request path.
Step 6 - egress
Private instances need patches, package mirrors and third-party APIs. Give each AZ its own NAT gateway in its public subnet and route that AZ's app subnet to it. Then remove the biggest NAT cost before it exists: gateway endpoints for S3 and DynamoDB (free, a route table entry) and interface endpoints for the AWS APIs the app calls (ECR, CloudWatch Logs, STS). See NAT gateways and egress and private service endpoints.
Step 7 - hybrid and other VPCs
Attach the VPC to a transit gateway, put a /28 subnet per AZ for the attachment, and connect on-premises with Direct Connect and a VPN as backup. This only works if CIDRs do not overlap, which is why Step 2 matters. Choices between peering, transit gateway and PrivateLink are in private connectivity.
Step 8 - DNS
A private hosted zone for internal names, and the VPC resolver (the .2 address of the VPC range, as the cloud network model shows) answering for both. Add Route 53 Resolver endpoints only if on-premises needs to resolve cloud names or the reverse (Route 53 Resolver).
Step 9 - observability
VPC Flow Logs to S3 (or CloudWatch Logs), ALB access logs, and Reachability Analyzer to prove a path exists before and after a change. When something breaks, the order of checks lives in debugging cloud networks (a companion page in this module) and in debugging by layer.
Step 10 - cost
Three line items, none visible in the diagram: NAT processing per GB, cross-AZ transfer, and interface endpoint hours. Rates are quoted in the cost worked example below, with the date and source.
Step 11 - failure modes
- AZ loss. Three AZs and capacity for N+1: the two survivors must carry the full load.
- NAT AZ loss. Per-AZ NAT and same-AZ routing mean only that AZ's private egress is lost, and that AZ is already down.
- DNS and limits. Resolver or quota exhaustion looks like an outage with no failed component. Know the quotas.
- Control plane. Do not rely on launching new capacity during an AZ failure; keep the spare capacity running.
Step 12 - at 10 times the scale
One VPC per environment per account, a shared networking account, a transit gateway as the hub, centrally managed egress and inspection, and IPAM for CIDRs. On GCP the same pressure leads to Shared VPC. See the multi-VPC whitepaper and the GCP-product view on common network designs.
Worked example: checking the CIDR plan
Every subnet must sit inside the VPC and none may overlap. Python's ipaddress module checks it, and also shows why 10.20.10.0/22 (a tempting number) is invalid: a /22 block must start on a multiple of 4 in the third octet.
The arithmetic, line by line:
- 15 subnets: 3 x 256 (public) + 3 x 1,024 (app) + 3 x 256 (data) + 6 x 16 (endpoints and TGW) = 768 + 3,072 + 768 + 96 = 4,704 addresses, which is 4,704 / 65,536 = 7.18 percent of the VPC.
- AWS keeps 5 addresses per subnet (subnet sizing, as of 2026-10), so a
/24has 256 - 5 = 251 usable, a/22has 1,024 - 5 = 1,019, and a/28has 16 - 5 = 11. - Growth headroom:
10.20.128.0/17is 32,768 addresses, half the VPC, and untouched. The app tier can grow by 32 more/22blocks inside that half before anything is renumbered.
Worked example: what one NAT per AZ costs
Rate assumptions (US East (Ohio), as of 2026-10, from AWS VPC pricing; check the page, rates differ by region): $0.045 per NAT gateway-hour and $0.045 per GB processed. With 730 hours in a month:
- Three NAT gateways idle: 3 x 730 x $0.045 = $98.55 per month before any traffic.
- Each GB through NAT adds $0.045, so 10,000 GB adds $450.00. The NAT page works the S3-endpoint saving in full.
That fixed $98.55 is the price of surviving an AZ failure with egress intact, which is the sentence to say when an interviewer asks "why not one NAT?". The scored checklist:
| Step | What to say | Red flag |
|---|---|---|
| 1 Requirements | Asks load, data sensitivity, RTO and RPO, on-prem link, compliance, Kubernetes or VMs | Starts drawing at once |
| 2 CIDR plan | Non-overlapping 10.x block, /16 VPC, per tier per AZ, headroom, IPAM | A /24 for the whole VPC, or 10.0.0.0/16 with no check |
| 3 Routing | Public to IGW, app to same-AZ NAT, data local only | All subnets in one shared route table |
| 4 Security | Security group chain by group reference, default NACL, nothing open on the data tier | Database in a public subnet with a strict group |
| 5 Ingress | ALB with TLS and WAF, NLB only for static IPs or raw TCP | Instances with public IPs behind no balancer |
| 6 Egress | NAT per AZ plus gateway and interface endpoints | One NAT for all AZs, no endpoints |
| 7 Hybrid | Transit gateway, Direct Connect with VPN backup, no overlap | Peering everything in a mesh |
| 9 Observability | Flow logs, ALB logs, Reachability Analyzer | No way to see traffic |
| 10 Cost | Names NAT per GB, cross-AZ, endpoint hours | Never mentions the bill |
| 11 Failure modes | AZ loss, N+1 capacity, what actually breaks | Says multi-AZ is automatic |
Deep dive
Variations the interviewer will throw in
- Kubernetes. Pods need their own addresses. On AWS with the VPC CNI they come from the node subnets, so size app subnets for nodes plus pods or add a secondary CIDR for pods; see Kubernetes networking and the EKS IP optimization guide.
- Multi-region active/active. Two copies of this design with non-overlapping CIDRs, connected by inter-region transit or peering, with traffic steering above it; see global traffic steering.
- Regulated workload. Remove the internet gateway from the app and data tiers, reach AWS services only through endpoints, and keep NAT out of the picture, as in the private EKS cluster pattern.
- Overlapping partner CIDR. Do not route to it. Expose the service through PrivateLink, which needs no routing between the two ranges; see private connectivity.
The scoring rubric interviewers use
Different companies word it differently, but the checklist below is what the scored table above encodes: requirements asked first, no single point of failure, least privilege, cost awareness, observability, and trade-offs named aloud. A design that is correct but silent on trade-offs scores lower than a slightly flawed one that explains its choices.
Cross-cloud name mapping
| Concept | AWS | GCP | Azure | Gotcha |
|---|---|---|---|---|
| Private network | VPC (one region) | VPC network (global) | Virtual Network (one region) | A GCP VPC spans regions, so a 3-region design is one network there |
| Subnet scope | One AZ | One region, all zones | Spans all AZs of the region | Subnet per AZ is an AWS habit, not a law |
| Internet entry | Internet gateway plus route | Default route to default-internet-gateway | Public IPs and default outbound path | GCP has no gateway resource to attach |
| Instance firewall | Security group (stateful, on the ENI) | VPC firewall rule or policy (stateful) | Network security group | Only AWS has a separate stateless subnet ACL layer by default |
| Private egress | NAT gateway | Cloud NAT on a Cloud Router | NAT Gateway on the subnet | Billing and port models differ |
| Private service access | Gateway and interface endpoints | Private Google Access, Private Service Connect | Service endpoints, Private Endpoint | Each needs DNS to resolve privately |
| Hybrid link | Direct Connect, Site-to-Site VPN | Cloud Interconnect, Cloud VPN | ExpressRoute, VPN Gateway | Dedicated links are slow to provision |
| Load balancer | ALB and NLB | Cloud Load Balancing | Application Gateway, Load Balancer | L7 and L4 are split differently per cloud |
| Many networks | Transit gateway, VPC sharing | Shared VPC | Hub VNet plus peering | Different tools for the same problem |
The GCP-product view of these designs lives in the gcp-ace VPC page and common network designs. Sources: AWS VPC, GCP VPC, Azure virtual networks.
What actually breaks when an AZ dies
- Compute in that AZ is gone. The auto scaling group launches replacements in the surviving subnets, but only if there is quota and free addresses. This is why survivors need spare capacity already running, and why the per-AZ headroom is a design input: with three AZs, steady-state load per AZ must stay under two thirds of its capacity so two AZs can carry everything.
- The AZ's NAT gateway goes with it, and so does egress for that AZ's subnets, which is acceptable because those instances are already gone. With a shared NAT in the dead AZ, every other AZ would lose egress too. That is the failure the per-AZ design prevents.
- Load balancer nodes in the AZ stop serving. Clients keep working because the balancer is a fleet spread across the enabled AZs and its DNS answer drops unhealthy nodes; the behaviour and the cross-zone setting are in cloud load balancers.
- Stateful systems are the real risk: a database primary in the failed AZ needs a standby elsewhere and a tested failover. A diagram with a database in each AZ but no replication story is incomplete.
Textbook vs real world
| The textbook says | Production does | Why |
|---|---|---|
| Public subnet and private subnet | Public, app, data, endpoint and transit-attachment subnets, each tier per AZ | Routing, security groups and blast radius differ per tier, and each attachment wants its own small subnet |
| One VPC | One VPC per environment per account, plus a shared networking account | Account boundaries are the strongest blast-radius control; a hub carries what must be shared |
| Size the subnets for today | Reserve unused ranges and plan growth up front | The VPC primary block cannot be resized and renumbering is a project |
| Security groups are enough | Groups for access, plus flow logs and an inspection point where policy requires | Groups cannot show what actually happened on the wire |
In production
- AWS publishes the pattern this page follows: separate accounts per workload with a shared network layer (Organizing your AWS environment) and a hub-and-spoke network in the multi-VPC whitepaper.
- The reliability pillar of the AWS Well-Architected Framework lists multi-AZ design and limit management as core practices (Reliability pillar).
- Google documents the equivalent for GCP in Best practices and reference architectures for VPC design, including Shared VPC for multi-project designs (Shared VPC).
- Security baseline. AWS's VPC security best practices cover the security group chain, flow logs and endpoint policies used above.
History and evolution
- 2000s The on-premises three-tier design: a DMZ VLAN for web servers, an application VLAN and a database VLAN, separated by firewalls. The tiers in this page are that design, with route tables and security groups in place of physical firewalls.
- 2009 Amazon launches VPC (announcement), giving customers private address space and a VPN link to their data centre instead of the shared flat network.
- 2018 AWS Transit Gateway (documentation) replaces meshes of VPC peerings with a regional hub, which is where the 12-step answer ends at scale.
- 2022 EC2-Classic is retired (AWS notice), leaving the VPC as the only network model.
- Now Account-per-workload landing zones with a shared networking account, IPAM-managed CIDRs, and endpoints instead of internet egress for AWS services.
- Next Identity-based access (zero trust) carrying more of the policy that subnets and CIDRs carry today, with the network as a coarse outer layer.
Hands-on
Carve the plan with Terraform's cidrsubnet(prefix, newbits, netnum); it gives exactly the table above. The skeleton below is a sketch, not a complete module.
Terraform's cidrsubnet(local.vpc_cidr, 6, 4) is 10.20.16.0/22: 6 extra bits make a /22, and block number 4 starts at 4 x 1,024 = 4,096 addresses into the VPC, which is 10.20.16.0. Verify what you built against the plan:
Look at AvailableIpAddressCount per subnet: it should be the usable count from the table minus anything already running (251 for a fresh /24). Then run the plan check from the worked example before applying, so an overlapping CIDR fails in review and not in terraform apply. Reachability Analyzer then confirms a path from the ALB to an app instance and proves that none exists from the internet to the database. These commands assume the AWS CLI v2; the Python check runs the same on macOS and Linux.
Gotchas
Interview questions
Show answer
Clarify load, sensitivity, availability and connectivity first. Then: VPC 10.20.0.0/16, per AZ a public /24 (ALB, NAT), an app /22 and a data /24, with headroom left. Public routes to the IGW, app routes to a same-AZ NAT, data is local only. Security groups chain ALB to app to database. ALB with TLS in front, gateway endpoints for S3 and DynamoDB, flow logs on. Close with what happens when an AZ dies.
Follow-ups:
- Why a
/22for app? It is the tier that grows, and pods or autoscaling consume addresses; 1,019 usable per AZ beats 251. - What would you change for Kubernetes? Size for pods or add a secondary CIDR, see Kubernetes networking.
Show answer
Route its subnet's 0.0.0.0/0 to a NAT gateway in a public subnet, which has a route to the internet gateway. NAT lets it start connections out and blocks unsolicited ones in. For HA, one NAT per AZ with same-AZ routing. For cost, NAT bills per hour and per GB, so move S3 and DynamoDB to gateway endpoints.
Follow-ups:
- What does three NATs cost idle? 3 x 730 x $0.045 = $98.55 per month at the US East (Ohio) rate, as of 2026-10 (pricing).
- Is there an alternative to the zonal gateway? AWS offers a regional NAT gateway mode; see NAT gateways and egress.
Show answer
A public IP is necessary, not sufficient. Check in order: the subnet's route table has 0.0.0.0/0 to an internet gateway, the security group allows the port from your source, the network ACL allows the port both ways (it is stateless, so the return ephemeral ports too), the instance's own firewall and service are listening, and the OS has a default route. Reachability Analyzer tests the first three without sending packets.
Follow-ups:
- Which check fails most? Security group source or a missing route, in that order for new instances; see security groups versus NACLs.
- Where is the structured version? In debugging cloud networks, the companion page in this module.
Show answer
Overlapping ranges cannot be peered or routed. The fast fix is PrivateLink: the partner exposes a service, you consume it through an interface endpoint with an IP from your own range, and no routes are exchanged. A NAT translating one side is a brittle workaround. The long-term fix is to renumber one side, and to allocate future ranges from IPAM.
Follow-ups:
- What does PrivateLink not give you? General any-to-any connectivity; it exposes one service, in one direction.
- How do you stop it recurring? A central IPAM and a rule that no VPC is created with a hand-picked block; see the cloud network model.
Show answer
A gateway endpoint adds a route to the S3 prefix list pointing at the endpoint, so S3 traffic leaves the subnet over the AWS network without NAT or an internet gateway, and it is free. Access control is a pair: an endpoint policy on the VPC side and a bucket policy that can require the request to come from that endpoint (S3 bucket policy for VPC endpoints).
Follow-ups:
- Does it work from on-premises? Not through a gateway endpoint; on-premises needs an interface endpoint, see private service endpoints.
- Why does it not use DNS? A gateway endpoint works through routing; interface endpoints work through DNS.
Show answer
Compute, NAT gateway and load balancer nodes in that AZ stop. Because every AZ has its own NAT and routes to it, the other AZs keep egress. The load balancer keeps serving from the surviving AZs, and the autoscaling group needs spare capacity there: with three AZs, steady load must stay under two thirds of per-AZ capacity. The hard part is state: the database needs a standby in another AZ and a tested failover.
Follow-ups:
- Why not rely on launching new instances? The failure may exhaust capacity or hit quotas exactly when everyone else is also launching, so keep spare capacity running.
- What is the cheapest improvement? Test it: drain one AZ in a game day and watch what breaks.
Show answer
Move to account-per-workload with a shared networking account holding a transit gateway. Each VPC attaches (a /28 subnet per AZ), Direct Connect with a VPN backup lands on the transit gateway through a gateway, and CIDRs come from IPAM so nothing overlaps. Egress and inspection are centralised in a dedicated VPC.
Follow-ups:
- How do you design the transit gateway route tables? Separate tables per trust zone, for example production, non-production and shared services, so propagation controls who can reach whom; see private connectivity.
- What changes on GCP? Shared VPC and VPC peering carry much of this; see common network designs.
Show answer
Measure first: flow logs and the billing breakdown by usage type, not guesses. Usual wins, in order: send S3 and DynamoDB through gateway endpoints, avoid cross-AZ NAT and cross-AZ chatter (same-AZ routing, zone-aware calls), cache or mirror big downloads, and drop idle NAT gateways in non-production. Weigh each: an interface endpoint costs an hourly fee per AZ.
Follow-ups:
- Where is it measured? Flow logs plus the cost and usage report, filtered by data transfer and NAT usage types.
- Which saving is a trap? Dropping to one NAT gateway: it saves $65.70 per month of fixed charge (2 x 730 x $0.045, as of 2026-10) and creates a single point of failure.
Fun facts
- A
/22is not valid at10.20.10.0: its start must be a multiple of 4 in the third octet, and Python rejects it with "host bits set", as the worked example shows. - Terraform's
cidrsubnet(prefix, newbits, netnum)carves exactly this plan, so the whole table can be generated from three integers per tier. - In this plan 92.8 percent of the VPC is unallocated, and an interviewer still may ask where you would put the next tier.
Cheat sheet
- /16 - the VPC block, 65,536 addresses
- 5 - addresses AWS reserves per subnet, so a
/24has 251 usable - 1,019 - usable addresses in a
/22 - 3 NATs idle - 3 x 730 x $0.045 = $98.55 per month (US East Ohio, as of 2026-10)
- 2/3 - maximum steady load per AZ if two of three AZs must carry everything