Looking back from 2025 Lambda won, and infrastructure-as-code removed much of the deployment pain. The useful part of this old workflow was not the ZIP upload. It was the pressure to keep each function small.
At a hackathon, I got tired of uploading ZIP files to AWS Lambda. After the third upload, I split one function into three smaller ones so I wouldn’t have to keep redeploying unrelated code. The bad deployment flow changed the design.
What Lambda Solves
Before Lambda, deploying backend code meant provisioning servers, managing OS updates and security patches, scaling infrastructure manually, and paying for idle capacity around the clock.
Lambda flips this. You write a function, upload it, and AWS handles everything else. You pay only for execution time, down to 100ms increments.
The promise was less infrastructure. In 2016, the tradeoff was a primitive deployment experience.
The Lambda Model
Google App Engine had already tried something similar; AWS just made the pattern stick. You send source code to AWS. It prepares it on a server and exposes an index.handler. This handler exposes APIs you call through REST (via API Gateway) or trigger from other AWS services.
A basic Lambda function in 2016:
exports.handler = function(event, context) {
console.log('Processing event:', event);
// Your business logic here
const result = processData(event.data);
context.succeed(result);
};
The complexity came from the deployment process.
The “Bad” Deployment Experience
The only way to deploy was uploading a ZIP file. Two options:
- Direct upload: ZIP your code and upload through the AWS Console
- S3 upload: Push ZIP to S3, then reference it in Lambda
There was no CI/CD integration, no automated testing, and no gradual rollouts — just ZIP and upload.
Coming from systems with git push deployments, this felt like a step backwards.
Why Friction Helped
Oddly, the terrible update interface helped me.
The deployment friction discouraged frequent deploys. I found myself splitting code into small, focused pieces. Once a piece of code reached stability, it didn’t need touching anymore. You don’t want to redeploy it just because adjacent code changed.
This constraint reinforced the core microservices idea: doing one small thing reliably.
Traditional deployment made it easy to keep adding to the existing service:
- “I’ll add this feature to the existing service”
- “Let me update this while I’m here”
Lambda’s friction made me ask different questions:
- “Does this deserve its own function?”
- “Can I make this completely independent?”
- “What’s the smallest unit I can deploy?”
The deployment pain made me think harder about boundaries. Each Lambda function became focused on a single responsibility.
How This Changed My Approach
The split was concrete:
auth-handler: Validates tokens, nothing elsedata-processor: Transforms input, no side effectsnotification-sender: Sends emails, decoupled from other logic
Each function could be updated independently. Each had a single reason to change. The friction forced better software engineering discipline.
Modern developer tools have smoothed deployment with Serverless, SAM, and Terraform. I wouldn’t choose the old ZIP flow over them. But that friction forced me to notice boundaries I had ignored when deployment was easy.
Lambda in 2016 vs. Today
The platform has matured since then—containerized functions, streaming responses, and better monitoring. I still remember the design change more clearly than whatever we built at the hackathon.
I use Cloudflare Workers for this site now. The deployment is easier, but I kept the lesson from that Lambda prototype: when unrelated code makes one deployment risky, the boundary is probably wrong.
One quick signal
Did this earn your time?
Thanks. That gives me something concrete to check.



