DEV Community

Cover image for 5 AWS Services New Developers Struggle With (And Why)
Ifeanyi O. for AWS

Posted on

5 AWS Services New Developers Struggle With (And Why)

Table of Contents

Intro

Okay... let's be honest for a second.

When you first opened the AWS Console, which service made you pause a little longer than the others? And I don't mean in a curious way but in a “wait… what the heck is actually going on here?” kind of way.

AWS is extremely powerful and that’s part of the appeal. But with great power and limited context, you can feel overwhelmed, especially when you’re no longer working in a personal account.

Yes, there are over 200 AWS services and most teams will rely heavily on a small subset but there are a select few that almost every new developer runs into early on and quietly struggles with.

It's usually not because they’re innately complex to understand, but because they require a mental shift most new developers haven’t made yet.

In this blog, we'll address five AWS services new developers struggle with which includes, AWS Identity and Access Management (IAM), Amazon Virtual Private Cloud (VPC), knowing the difference between Amazon Simple Queue Service (SQS) and Amazon Simple Notification Service (SNS) and when to use which, AWS CloudFormation and Amazon CloudWatch.

IAM

AWS Identity and Access Management is the control plane gatekeeper. Every action performed in AWS eventually becomes an API call and every API call is evaluated by the IAM policy engine before it is allowed to proceed.

This is where new developers first encounter friction because IAM is deterministic but not always obvious. When you receive an AccessDenied error, this is the result of a precise evaluation process that has determined the request should not be allowed.

When a request is made, AWS evaluates the principal making the request, the action being requested, the resource being accessed and any conditions that apply. That principal could be an IAM user, an assumed role, a federated identity, or a service role attached to a resources like an AWS Lambda function or Amazon EC2 instance. Developers often forget that once code runs in AWS, it is no longer acting as "you" but actually acting as the role attached to the compute resource.

IAM evaluation considers identity-based policies attached to the principal, resource-based policies attached to the target resource, permission boundaries that may restrict the maximum allowed permissions of that principal and Service Control Policies (SCP) applied at the organizational level. If any applicable policy contains an explicit deny that matches the request, the request fails immediately, even if other policies allow it.

This layered evaluation model is what confuses many developers. A developer may attach a policy granting permission to read from an S3 bucket, but the bucket policy may restrict access to requests originating from a specific VPC endpoint, or the organization may enforce an SCP preventing certain API calls entirely, or the role being assumed in a CI/CD pipeline may not include the permissions the developer tested locally.

To make this more concrete, let's consider a simple identity-based policy that allows reading from an S3 bucket:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-app-bucket/*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

At a glance, this policy appears sufficient as it allows the required action on the expected resource. However, IAM does not evaluate this policy in isolation. If a bucket policy restricts access to a specific network path, or an organizational policy blocks the action entirely, or the request is coming from a different role than expected, the final decision may still be a denial. The presence of an allow statement does not guarantee access if another layer introduces a conflicting rule.

IAM also introduces conditional logic. Access can be restricted based on IP address, multi-factor authentication presence, encryption state, resource tags, or request context. Two seemingly identical API calls can produce different results based on environment.

Let's review this updated identity-based policy that introduces an additional requirement:

{
  "Effect": "Allow",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-app-bucket/*",
  "Condition": {
    "Bool": {
      "aws:SecureTransport": "true"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The same request will succeed or fail depending on whether it is made over HTTPS.

For most developers, what makes IAM difficult early is that it forces you to think like a policy engine and start asking questions like, “Which principal is making this request and which policies are being evaluated?”

However, once you understand the deterministic evaluation system of IAM policies, your debugging episodes will become more systematic and efficient.

VPC

Many developers don’t think deeply about networking early in their careers. Things usually just connect locally, APIs call databases and everything just works. However, when you join an actual team and start hearing phrases like, “It's in a private subnet,” or “The security group won’t allow that traffic,” or “There’s no route to the NAT gateway,” that's when you realize your lack of depth in cloud networking concepts and resources.

For understanding, a VPC defines the networking boundaries of your AWS environment. It determines which resources are public, which are private, how traffic flows and what is allowed to communicate with what. Subnets, route tables, internet gateways, NAT gateways, security groups and network ACLs all play a role.

The reason new developers struggle with VPC is that networking is often abstracted until things begin to fail. You might deploy a Lambda function and wonder why it can’t connect to a database. Both the function and the database exists but if they’re in different subnets without proper routing or security group rules, they might as well be on separate planets.

On AWS, connectivity explicit. Just because two resources exist does not mean they automatically can talk to each other. Traffic MUST be intentionally allowed.

Let's consider what happens when a resource needs internet access from a private subnet. That path does not exist by default and must be explicitly defined through routing.

Below is an example of a route table entry that allows traffic from a subnet to reach the internet through a NAT Gateway:

Destination: 0.0.0.0/0
Target: nat-0abc123example
Enter fullscreen mode Exit fullscreen mode

This single route determines whether instances in that subnet can reach external services like APIs, package repositories, or external databases. Without it, outbound traffic has nowhere to go, even if everything else appears to be configured correctly.

Similarly, security groups act as virtual firewalls at the resource level. Even if routing is correct, traffic can still be blocked if the security group does not explicitly allow it.

For example, a database security group might only allow inbound traffic on port 5432 from a specific application security group:

Type: PostgreSQL
Port: 5432
Source: sg-0appserver123
Enter fullscreen mode Exit fullscreen mode

In this case, even if another resource is in the same VPC and subnet, it will not be able to connect unless it is associated with the allowed security group so connectivity cannot be assumed. It has to be defined.

As a developer trying to grasp networking on AWS, at first, it might feel like a maze with invisible walls... and that's okay. Once you start visualizing the path a request takes, from the client through a load balancer, into a subnet, through a security group and into a database, understanding VPC will become easier to reason about.

SQS vs SNS

Almost every new developer gets confused by Amazon SQS and Amazon SNS at some point. They both move messages, are used in event-driven systems and are often mentioned together in architectural diagrams. So the question is... what’s the difference?

For starters, Amazon SQS is basically a queue. Messages sit there until something pulls them and processes them. On the contrary, Amazon SNS is a publish-and-subscribe service where messages are pushed to subscribers when they are published.

This sounds simple, but the confusion comes from how they’re used together. You’ll see architectures where an S3 event triggers an SNS topic, which then fans out to multiple SQS queues, then trigger Lambda functions or you’ll see an SNS topic directly invoking endpoints and maybe EventBridge involved somewhere in the mix. As more services are integrated, suddenly, the flow of data feels less obvious.

Let's review what it looks like to publish a message to an SNS topic:

import boto3

sns = boto3.client('sns')

response = sns.publish(
    TopicArn='arn:aws:sns:us-east-1:123456789012:my-topic',
    Message='Order created'
)
Enter fullscreen mode Exit fullscreen mode

The moment this message is published, SNS immediately pushes it to all of its subscribers. That could include multiple SQS queues, Lambda functions, or even HTTP endpoints. The publisher does not need to know who those subscribers are, it simply emits the event.

Now compare that to SQS. Messages are not pushed to consumers, they remain in the queue until something explicitly retrieves (pulls) them:

import boto3

sqs = boto3.client('sqs')

response = sqs.receive_message(
    QueueUrl='https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
    MaxNumberOfMessages=1
)

messages = response.get('Messages', [])
Enter fullscreen mode Exit fullscreen mode

In this case, the consumer is responsible for polling the queue, retrieving messages and processing them. If no consumer is running, the message simply stays in the queue.

The real struggle here is not in the intricacies of the syntax of the services, it's more about the architectural thinking that's require here. You have to ask questions like, "Do I need multiple consumers?", "Do I need messages to persist until they’re processed?", "What happens if a consumer fails?", "Will this message be retried?", "Could it be processed twice?" and this is where the distinction becomes meaningful.

SQS gives you durability and control over processing. Messages persist until they are handled, which makes it useful when you need reliability and buffering between systems. SNS gives you immediacy and fan-out. It is designed to broadcast events to multiple systems at once without tightly coupling them.

Additionally, messaging introduces distributed systems concepts like at-least-once delivery and idempotency. For many developers, this is the first time they have to think beyond simple request-response patterns like when a message may be delivered more than once, a consumer may fail mid-processing, or systems may process events at different speeds.

This is how SQS and SNS can feel confusing complex early because the real challenge is understanding how they behave when systems are no longer tightly connected and operations don't happen in a single request-response cycle.

CloudFormation

At some point, you’ll try to modify a resource in the AWS Console and get a message telling you that the resource is managed by CloudFormation and that's you'll realize that you’re interacting with infrastructure defined as code.

AWS CloudFormation allows teams to define infrastructure in templates and deploy it as stacks and those stacks become the source of truth. If something is defined in a template, manual console changes are temporary at best and overwritten at worst.

New developers struggle here because it requires discipline and patience. You can’t just click and tweak something quickly, you need to find the template, understand how the resource is defined, update it properly and redeploy the stack. Additionally, when a stack update fails, the error messages can be intimidating.

Let's look at a simple CloudFormation template that defines an S3 bucket:

Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-app-bucket-123
Enter fullscreen mode Exit fullscreen mode

This template does not describe how to create the bucket step by step. Instead, it defines that a bucket with this name, my-app-bucket-123, should exist. When deployed, CloudFormation creates and manages that resource as part of a stack.

If you later change the bucket name in the template and redeploy, CloudFormation will determine what needs to change to match the new desired state. That might mean replacing the resource entirely, which is why understanding updates is just as important as initial creation.

CloudFormation forces you to think declaratively. Instead of saying, “Create this resource,” you define the desired state and let AWS reconcile the difference. It’s a powerful approach, but it changes how you work. It also means most of what you’re touching likely existed before you arrived and was created intentionally through code.

New developers have to intentionally learn to stop treating the console as the place to make changes and start treating CloudFormation templates as the source of truth. Once you understand that every update goes through the stack, even when something fails, you'll know where to look to fix it.

CloudWatch

As a new developer, you'll be focusing on building features so observability can sometimes feel like a secondary task. However, the minute something breaks in production, then suddenly logs and metrics are all anyone cares about.

Amazon CloudWatch is where you go to understand what your systems are actually doing in real time as it handles logs, metrics, alarms and dashboards but it does not interpret the data for you. What you get is raw signals that you have to make sense of them.

You might open a log group and scroll through hundreds of lines without knowing what you’re looking for, or see a metric spike but not know what "normal" even looks like, or even wonder why an alarm didn’t trigger when you expected it to.

Let's look at what a simple log entry from a Lambda function might look like:

2026-03-31T14:22:10.123Z  ERROR  Failed to process orderId=12345
Traceback (most recent call last):
  File "/var/task/app.py", line 42, in handler
    process_order(order)
Exception: Database connection timeout
Enter fullscreen mode Exit fullscreen mode

At a glance, this tells you something failed, but in a distributed system, this is rarely the full story. The database might be in a different subnet, behind a security group, or experiencing latency issues, so the log gives you a clue, but it's not the complete answer.

Now consider a metric that tracks errors over time:

Metric: Errors
Namespace: AWS/Lambda
Statistic: Sum
Period: 5 minutes
Enter fullscreen mode Exit fullscreen mode

If this metric spikes, CloudWatch can trigger an alarm:

Alarm: LambdaErrorAlarm
Threshold: Errors > 5
Evaluation Period: 1 datapoint
Action: Send notification to SNS topic
Enter fullscreen mode Exit fullscreen mode

Even here, interpretation is required. Is five errors in five minutes abnormal? Is that a real issue or expected behavior during traffic spikes? CloudWatch provides the signal, but it does not define what is meaningful.

The challenge here is that debugging in distributed systems is very different from debugging locally. There’s no single stack trace that tells the whole story and you have to piece together information across services.

The good news is overtime, you will build intuition and learn which metrics matter, how to structure logs and when to create alarms. But early on, CloudWatch can feel overwhelming as it exposes the complexity of production systems in it's full detail.

Why These Five Services Matter

As a new developer, these five services show up early because they sit at the foundation of how systems are built and operated.

IAM teaches you to think in terms of identity and authorization, VPC teaches you to understand network boundaries and traffic flow, SQS and SNS introduce you to distributed and event-driven thinking, CloudFormation forces you to respect infrastructure as code and existing systems and CloudWatch pushes you to think operationally and observe before you optimize.

They represent transitions in how you think as a developer working in the cloud.

If you’re struggling with one of them right now, don't worry. It’s usually a sign that you’re growing. Every developer who works seriously in AWS goes through a growing phase. The difference over time is that what once felt confusing will eventually become familiar.

Lastly, if you're looking to dive deeper in your learning journey, explore these FREE AWS resources:

Good luck!


If you've got this far, thanks for reading! I hope it was worthwhile.

Top comments (0)