Question presented to candidate: "Your Express app runs great on a long-lived server. You are asked to port its logic to a single AWS Lambda function. What actually has to change, beyond swapping app.listen() for a handler export?"
What a strong answer should cover:
- The fundamental shift: a long-running server has one process serving many requests over its entire lifetime. Lambda instead runs your code inside an execution environment that AWS may reuse across several invocations (a "warm" invocation) or may need to create fresh (a "cold start") β and your code has no control over which one happens for a given request.
- π Execution context reuse: code written outside the handler function β imports, a database client, a computed constant β runs once per execution environment, not once per invocation. This is the single most important practical consequence: a DB connection pool, an HTTP keep-alive agent, or an expensive computed value should be initialized at module scope specifically so warm invocations can reuse it, instead of re-paying that cost on every request.
- Cold starts are a real, measurable cost specific to Lambda that a long-running server simply does not have: the first invocation on a fresh execution environment pays for loading the runtime, your code, and any module-scope initialization, before your handler logic even starts.
- A long-running server can hold in-memory state across requests indefinitely (an in-memory cache, a queue) with a guarantee that it persists as long as the process is up. Lambda offers no such guarantee β an execution environment can be reused for a while, then discarded, with no notification to your code; anything relying on in-memory state surviving between requests needs an external store instead (a database, Redis, S3).
- Lambda gives each execution environment a fixed amount of
/tmpephemeral disk (512 MB by default, configurable up to 10,240 MB / 10 GB as of this writing) that similarly may or may not persist between invocations depending on reuse β treat anything written there as disposable, not durable. - The handler execution model itself changes control flow: a Lambda handler is invoked once per event and expected to return (or resolve/call a callback) to signal completion β there is no long-lived event loop sitatting idle between requests the way
app.listen()provides; any work intentionally left running after the handler returns (an unawaited promise, a timer) is not guaranteed to execute, since the environment can be frozen the instant the handler signals completion.
Clarifying questions expected:
- "Does anything in the current implementation rely on in-memory state surviving between requests?" β that specific assumption breaks under Lambda's execution model.
- "Is this a latency-sensitive, user-facing path where cold starts would be user-visible?" β decides how much cold-start mitigation (provisioned concurrency, smaller bundles) is worth the added complexity.
Code / implementation expected: Optional, but demonstrating the module-scope-reuse mechanism itself β the actual reason connection pooling works on Lambda β with real, observable process behavior is the most convincing thing to show.