The word serverless sounds as if there are no servers at all. That is not the case — the servers are there, you just don't see them and don't manage them. AWS spins up a machine when your code needs to run and shuts it down when the work is done. You don't patch the operating system, you don't configure scaling, you don't pay for idle instances — you pay only for the actual time your code was running.

The main building block of serverless on AWS is Lambda: a service where you upload a function (a piece of code), and AWS runs it in response to an event. This approach is often called FaaS — Function as a Service. If you haven't yet looked into what options exist for running code in the cloud, it helps to start with the overview of AWS compute — there Lambda sits alongside virtual machines and containers. Here we'll dig into how serverless works under the hood and when it's worth choosing.

The event-driven model

An ordinary server is a program that runs continuously and waits for requests. Lambda works the other way around: the code "sleeps" until an event happens — something the function is supposed to react to. An event occurs — AWS starts the function, it does its work and terminates. No events — no running code and no bill for work.

There are many sources of events, and that's the strength of the model. A few typical ones:

  • an HTTP request from a user — arrives via API Gateway (more on it below);
  • a new message in an SQS queue;
  • a file uploaded to S3 storage (for example, an image was uploaded and the function created a thumbnail);
  • a record changed in a DynamoDB database (via DynamoDB Streams);
  • a scheduled time arrived (once an hour, every night);
  • an event arrived on the EventBridge bus from another service.

A key property follows from this model — automatic scaling. One request came in — AWS started one copy of the function. A thousand requests came in at once — it starts a thousand copies in parallel. You don't need to configure auto-scaling groups and load balancers as you would for ordinary servers (this is covered in detail in the article on scaling and availability). That's why uneven, "spiky" load — sometimes empty, sometimes packed — is Lambda's natural element.

Cold start

The event-driven model has a downside, and it's the thing people ask about most often — the cold start.

Let's look at the mechanics. When a function finishes, AWS doesn't kill it instantly — it keeps a "warm" execution environment around for a while so the next invocation is fast (this is called a warm start). But if there are no invocations for a long time, the environment "cools down" and is removed. Then the next invocation requires bringing everything up again: allocating a machine, downloading your code, starting the runtime (the language execution environment), running initialization — database connections, reading configuration. All this preparation is added to the response time of the first request. That added time is the cold start.

An analogy: a warm start is a car that's already running and driving. A cold start is when the car sat all night in the cold, and first you have to start it and warm it up. It drives the same afterward, but the first trip after sitting idle takes longer.

How people deal with it:

  • Provisioned concurrency — keep a set number of "warm" functions ready in advance. They don't cool down, so the cold start disappears — but you have to pay for that readiness, even when there are no invocations.
  • Fewer dependencies and a lighter package — the less code and fewer libraries need to be loaded and initialized, the faster the start.
  • SnapStart — the function is initialized once, AWS takes a "snapshot" of the ready environment and then launches copies from it, skipping the slow initialization. This capability is available for several runtimes (Java, Python, .NET) and noticeably reduces cold start for them without paying for provisioned concurrency.
  • A lightweight runtime — with some languages the execution environment starts in milliseconds, while with other "heavy" runtimes the startup of the environment itself is noticeably longer. For synchronous APIs with strict response-time requirements this is taken into account when choosing a language.

An important practical takeaway: cold start is quite tolerable for background and event-driven processing (no one will notice if parsing a file starts half a second later), but it's unpleasant for a synchronous API where the user is waiting for a response right now. This is one of the main criteria for whether or not to use Lambda for a task.

Lambda limits

A function has hard limits that are important to know in advance, so you don't hit them at the worst possible moment:

  • Execution time — a maximum of 15 minutes (900 seconds) per invocation. This is a ceiling, it can't be raised. Long tasks simply don't fit on Lambda.
  • Memory — up to 10,240 MB per function. And CPU power is tied to memory: the more memory you allocate, the more vCPU the function gets. Sometimes you add memory not for its own sake, but to speed up computation.

These limits are another reason not to try to cram everything into Lambda. If a task runs longer than 15 minutes or requires constant heavy work — that's a signal to look at containers or virtual machines.

API Gateway and Step Functions

Lambda rarely lives alone. Two services complement it most often.

API Gateway is a managed "front door" for HTTP requests in front of your functions. Lambda by itself can't accept requests from the internet at an address like https://api.example.com/orders — it has no web server of its own. API Gateway takes that on: it receives the HTTP request, checks authorization, limits how often it can be called (rate limiting), routes the request to the right function, and returns the response to the client. This turns a set of functions into a full-fledged REST API.

The simplest function invocation via the AWS CLI looks like this:

aws lambda invoke --function-name create-order --cli-binary-format raw-in-base64-out --payload '{"item":"book"}' response.json

The --cli-binary-format raw-in-base64-out flag is mandatory here. In AWS CLI version 2 (installed by default) the --payload parameter expects data in base64, and if you pass raw JSON without this flag, the command fails with an Invalid base64 error. The flag tells the CLI to accept the JSON as-is. To avoid adding it every time, you can run aws configure set cli-binary-format raw-in-base64-out once. In the older version 1 the command worked without the flag — hence the old examples that no longer run on CLI 2.

Step Functions is an orchestrator for multi-step processes. Imagine a scenario: "accept the order → charge the money → reserve the item → send an email, and if some step fails — roll back." You could write this as a chain of functions calling each other, but such homegrown logic quickly turns into a tangled mess where it's unclear at which step everything got stuck.

Step Functions describe a process as a state machine: an explicit sequence of steps, branches ("on success — this way, on error — that way"), retries, and pauses between steps. The logic becomes clear, and in the AWS console you can see which step is running now and where something went wrong. The state machine is described declaratively, in JSON format:

{
  "StartAt": "ChargePayment",
  "States": {
    "ChargePayment": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:123456789012:function:charge",
      "Next": "ReserveItem"
    },
    "ReserveItem": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:123456789012:function:reserve",
      "End": true
    }
  }
}

When serverless is justified and when it isn't

The main skill is not "being able to write Lambda," but understanding where it fits and where it will do harm.

Serverless is good when:

  • the load is event-driven or "spiky" — sometimes empty, sometimes a surge; paying for idle time is a shame, and peaks can't be predicted;
  • tasks are irregular — a nightly report export, reacting to a file upload, processing messages from a queue;
  • you need to glue several services together with a small piece of code;
  • the work is background, and fractions of a second of cold-start delay bother no one.

Serverless loses when:

  • the load is constant and high — then it's more predictable and cheaper to keep containers or instances that run all the time;
  • tasks are long — they hit the 15-minute limit;
  • you need a very fast and stable response from a synchronous API, and cold start gets in the way;
  • the task has complex state that's awkward to keep in an ephemeral function that lives only for the duration of the invocation.

The main idea: serverless is a tool for a certain class of tasks, not a replacement for everything else. "Everything on Lambda" is just as much an extreme as "nothing on Lambda." You choose deliberately, based on the nature of the load.

Where this is used

Serverless shows up everywhere there's event-driven or irregular work: processing files uploaded by users, reacting to messages in queues, scheduled nightly jobs, gluing microservices together, chatbots, webhook receivers. Many projects use Lambda not as the foundation of the whole system, but selectively — where a constantly running server would be a pure waste of money.

Typical beginner mistakes:

  • Ignoring cold start until users complain about a slow first response. If you're building a synchronous API with a speed requirement — plan for provisioned concurrency or SnapStart from the start, not as a firefighting measure.
  • Trying to cram a long task into Lambda and hitting the 15-minute limit in the middle of processing. Long processes belong in Step Functions, containers, or dedicated batch-processing services.
  • Piling all the logic into one giant function. Lambda is good with small, focused functions — they both start faster and are easier to test.
  • Forgetting about the cost of provisioned concurrency — it's kept running at all times, and the bill accrues even without invocations. This hits your costs if you turn it on "just in case" and forget.

What to learn next. To see the full map of options for running code, go through the compute overview and scaling and availability — you'll understand how Lambda differs from servers and containers. For storing data behind serverless functions, the natural companion is DynamoDB, a database that scales the same way without managing servers. It's worth looking at access management via IAM: functions need permissions to read a queue or write to a database. And to avoid describing functions, gateways, and state machines with clicks in the console, learn infrastructure as code — start with the basics of IaC and Terraform or CloudFormation.