Serverless architecture is a model in which you write and deploy code without managing or provisioning servers: the cloud provider takes complete care of the infrastructure, runs your code only when needed, and charges you only for actual execution time, usually in milliseconds. The name is misleading: of course there are servers, but you neither see nor administer them. Its purest expression is FaaS (Functions as a Service), where you deploy small functions that react to events. It matters because it lets you build systems that automatically scale from zero to thousands of executions, with zero cost when there is no traffic, and it drastically reduces the operational burden. But it is not a silver bullet: it has significant limitations (the cold start, maximum execution times, provider dependency) that you must know in order to decide when to apply it.

Contents

  1. What serverless is and what FaaS is.
  2. Event-driven architecture.
  3. The cold start problem.
  4. Advantages and limitations.
  5. Example of a serverless function.
  6. When to use serverless and when not to.
  7. Common mistakes and tips.
  8. Exercises and solutions.

  1. What serverless is and what FaaS is

"Serverless" is an umbrella that includes several types of managed services that scale on their own and are paid per use: serverless databases, message queues, object storage, etc. Its most representative component is FaaS (Function as a Service): you upload a function —a small, stateless unit of code— and the provider runs it in response to an event (an HTTP request, an uploaded file, a message in a queue, a timer). Examples: AWS Lambda, Azure Functions, Google Cloud Functions.

Characteristic Traditional server / VM Container (K8s) Serverless (FaaS)
You manage the OS Yes Partial No
Scaling Manual or configured Configured Automatic (even to zero)
Cost without traffic You pay anyway You pay anyway Zero (or almost)
Deployment unit Server/app Container Function
State Can have Can have Stateless
Control High Medium Low

  1. Event-driven architecture

Serverless shines in event-driven architectures: functions are not "always on" waiting, but rather activate when a trigger fires. This fits naturally with asynchronous, decoupled flows.

graph LR
    A[User uploads image] --> B[S3 Storage]
    B -- ObjectCreated event --> C[Function: generate thumbnail]
    C --> D[Save thumbnail to S3]
    C --> E[Message queue]
    E --> F[Function: notify user]

Explanation of the diagram: the user uploads an image to storage. That action emits an event that triggers a function to generate a thumbnail. The function saves the result and publishes a message in a queue, which in turn triggers another function that notifies the user. Each piece is small, independent, and scales separately. There are no servers waiting: the code only runs when something happens.

  1. The cold start problem

When a function has not been used recently, the provider does not have an environment ready for it. On the first invocation it must: provision a container, load the runtime (Node, Python, Java…), and initialize your code. That initial delay is the cold start and can range from tens of milliseconds to several seconds. Subsequent invocations, while the environment remains "warm," are fast (warm start).

Factor Effect on the cold start
Language Java/.NET usually take longer; Node/Python/Go start fast
Package size The larger it is, the longer it takes to load
Allocated memory More memory usually provides more CPU and faster startup
Dependencies and initialization Connections and heavy libraries at startup penalize it

Common mitigations: keep functions small, choose lightweight runtimes, use provisioned concurrency (pre-warmed environments paid for separately), and reuse connections outside the handler so they persist across warm invocations.

  1. Advantages and limitations

Advantages Limitations
Automatic scaling, even to zero Cold start: latency on the first invocation
Pay for actual usage (no cost at rest) Execution time limit (e.g., minutes, not hours)
Zero management of servers and patches Stateless: you need external storage
Fast, granular deployments Provider dependency (vendor lock-in)
Managed high availability More complex debugging and monitoring
Ideal for irregular or spiky workloads Costly if traffic is high and constant

  1. Example of a serverless function

An AWS Lambda function in Node.js that responds to an HTTP request. The handler is the entry point that the provider invokes with the event (trigger data) and the context (execution metadata):

// index.js
// Reusable connection: declared OUTSIDE the handler to
// take advantage of "warm starts" and avoid reconnecting on every invocation.
const db = require("./db").connect();

exports.handler = async (event, context) => {
  try {
    // 'event' contains the trigger data (e.g. the HTTP request)
    const userId = event.queryStringParameters?.id;

    if (!userId) {
      return { statusCode: 400, body: JSON.stringify({ error: "Missing id" }) };
    }

    const user = await db.getUser(userId);

    // Response in the format the API Gateway expects
    return {
      statusCode: 200,
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(user),
    };
  } catch (err) {
    console.error("Error in the function:", err);
    return { statusCode: 500, body: JSON.stringify({ error: "Internal error" }) };
  }
};

Explanation of the code:

  • The line const db = require("./db").connect(); is outside the handler on purpose: the initialization code runs only once per environment and is reused across warm invocations, avoiding reconnecting each time. It is a key optimization in serverless.
  • exports.handler = async (event, context) => {...}: the handler is the function the provider calls. event carries the trigger data; context carries metadata (remaining time, identifiers).
  • The input is validated and, if the id is missing, it responds with HTTP code 400 (bad request).
  • The data is looked up and an object with statusCode, headers, and body is returned. The provider (via API Gateway) translates that object into a real HTTP response.
  • The catch block logs the error and returns 500 so as not to expose internal details.

To define the HTTP trigger declaratively with the Serverless Framework:

service: users-api
provider:
  name: aws
  runtime: nodejs20.x
  memorySize: 256          # MB of memory (affects CPU and cold start)
  timeout: 10              # maximum execution seconds
functions:
  getUser:
    handler: index.handler # file.function
    events:
      - http:
          path: /users
          method: get        # GET /users triggers the function

Explanation: runtime sets the execution environment; memorySize and timeout configure resources and the time limit; handler points to index.js and the handler function; the events block declares that a GET /users request invokes the function. The framework takes care of creating the Lambda, the API Gateway, and the permissions.

  1. When to use serverless and when not to

Good use cases:

  • APIs and backends with irregular or unpredictable traffic (occasional spikes).
  • Event processing: resizing images, processing uploaded files, reacting to messages in a queue.
  • Scheduled tasks (cron) and one-off automations.
  • Webhooks and lightweight integrations between systems.
  • Prototypes and MVPs where you want to move fast without setting up infrastructure.

Bad use cases:

  • Applications with high, constant, and predictable traffic: at that volume a reserved server or container is usually cheaper.
  • Long processes (above the function's time limit) or with sustained intensive CPU/memory use.
  • Workloads with critical, strict latency where the cold start would be unacceptable.
  • Applications with persistent in-memory state (functions are ephemeral and stateless).

Common Mistakes and Tips

  • Putting database connections inside the handler. Declare them outside to reuse them across warm starts, and watch out for connection pool exhaustion, a classic problem when many functions scale at once.
  • Functions that are too large (monolithic functions). Keep each function focused on a single task; large packages worsen the cold start.
  • Ignoring cost at scale. Serverless is cheap with little traffic, but with millions of constant invocations it can be more expensive than a container. Measure.
  • Not designing for idempotency. Events can be delivered more than once; your functions must tolerate repeated executions without duplicate effects.
  • Forgetting observability. Being distributed and ephemeral, without proper traces and metrics debugging is very difficult. Instrument from the start.
  • Tip: embrace event-driven decoupling, but document the flow well: a tangle of functions without a diagram becomes unmanageable.

Exercises

  1. Explain why declaring the database connection outside the handler improves performance, and in what type of invocations it is noticeable.
  2. A company has an internal API with constant and high traffic 24 hours a day. Would you recommend serverless? Justify your answer.
  3. Describe a serverless event-driven flow to process invoices that are uploaded to storage: which triggers and functions would you chain together?

Solutions

  1. Because the code placed outside the handler runs only once when the environment is initialized and is reused while the environment remains warm, avoiding reopening the connection on each call. The improvement is noticeable in consecutive warm invocations (warm starts); on the first cold start initialization is still required.
  2. No, probably not. With constant, high traffic 24/7, the pay-per-invocation model tends to be more expensive than a reserved container or server, which also avoids the cold start entirely. Serverless performs better with irregular or spiky traffic.
  3. A possible flow: (1) the user uploads the invoice to object storage, which emits an ObjectCreated event; (2) that event triggers a validation/extraction function that reads the PDF and obtains the data; (3) the function publishes a message in a queue; (4) that message triggers another recording function that saves the data to a database and, optionally, triggers a third notification function. Each step is independent, asynchronous, and scales separately.

Conclusion

You have understood the serverless model and its FaaS core, event-driven architecture, the cold start challenge, the balance of advantages and limitations, how a function is written, and above all when it is appropriate and when it is not. Serverless is a powerful tool for irregular workloads and event-driven flows, not a universal solution. In the next lesson we will gather cross-cutting best practices for building robust applications in the cloud with the cloud-native design patterns.

Application Architecture Course

Module 1: Fundamentals of Application Architecture

Module 2: Design Principles and Tactics

Module 3: Architectural Styles and Patterns

Module 4: Distributed Architectures and Microservices

Module 5: Event-Driven Architectures and Messaging

Module 6: Domain-Driven Design (DDD)

Module 7: Data and Persistence

Module 8: Cloud Architecture and Deployment

Module 9: Quality, Security and Observability

Module 10: Evolution, Governance and Case Studies

© Copyright 2026. All rights reserved