Photo by Nathan Riley from Unsplash

3 Things I Wish I Knew About AWS Lambda Functions Early On

So I’ve been building serverless applications for quite a lot during the last year and a half. It’s now time to share a few very important takeaways I’ve learned about the AWS Lambda engine.

Retaining of the execution context

/* my-lambda-function.js */const Logger = require('...');
const logger = new Logger('...');
exports.handler = () => {
// ...

You might be doing something similar in your code where you create a global instance of an object (for example a reusable logger instance) that is then passed around or exported for use in other listings.

Since it has no modifiable state you treat it as a global constant and don’t consider it an anti-pattern. And that’s where the problems begin: this is an anti-pattern in the context of the Lambda engine.

When someone comes around and unknowingly updates the Logger class to hold some modifiable state, it infests your code with one of the evilest bugs that you could ever encounter: implicit data mutations. You can’t understand the source of the mutations and spend hours trying to fix it.

It’s not immediately obvious because intuitively you treat each Lambda function as a separate application and, again intuitively, you expect it to start each code execution from a blank slate, with a brand new global context. It’s so intuitive that you don’t even think about it.

Well the truth is that Lambda can actually run the next function invocation request on an existing, running engine image, on the same Node.js process by simply invoking your Lambda function handler method with a new request payload. If you modified your global context in the previous execution, effects of that will propagate into the future executions that occur afterwards.

It’s worth noting that this “request bundling” happens only when two requests occur within a certain time threshold, that in my experience have been about 2–3 seconds.

Abandoned network connections

When you write your Lambda function code, you use callbacks or Promises to signal the Lambda engine that your function is done doing what it’s been doing and a response can be returned back to the invocation request.

Intuitively you expect that when your handler’s returned Promise resolves or rejects, or when you call a handler callback() function, your function will stop executing and pass back the execution result. But it’s not that simple.

If your Node.js event loop contains something at the time of callback() invocation or Promise resolution Lambda engine won’t act on your signal until the event loop is cleared. Typically things that are found in the event loop once your script flow is done executing are open database connections. If you don’t terminate them and leave them hanging your function will timeout.

You can disable this behavior by setting the callbackWaitsForEmptyEventLoop flag to false on the context object passed to your Lambda function handler.

/* my-lambda-function.js */exports.handler = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;

Lambda layers limitations

I wrongfully assumed that layer count is unlimited and that function size is not a thing. I jumped straight into it and ended up designing several serverless applications incorrectly which led me to have to redo my work.

So remember these limitations and plan your application architecture ahead, taking this information into consideration. Don’t repeat my mistakes!

If you have found this article valuable please share it! Thank you for reading!

Writing full-stack React.js applications and building cloud solutions on AWS. Find me on GitHub and LinkedIn @iamarkadyt or at