Cloud platforms (AWS, Azure, Google Cloud) · Intermediate
An event-driven function with a least-privilege role
Why it matters
Managed and serverless services let small teams ship without running servers, and job descriptions for cloud, DevOps and data engineering roles name Lambda, Azure Functions and Cloud Run constantly. The two skills interviewers probe are wiring services together with events, and granting only the permissions each piece needs.
The concept
Event-driven: a new object in storage, a queue message or an HTTP request triggers a function. You pay per invocation and duration, not for idle servers.
Function: a handler that receives an event and returns a result. Keep it small, stateless and idempotent (safe to run twice on the same event, because retries happen).
Execution role: the identity the function runs as. Its policy should allow exactly the actions and resources the function uses: least privilege.
The same pattern exists everywhere: S3 plus Lambda on AWS, Blob Storage plus Functions (Event Grid) on Azure, Cloud Storage plus Cloud Run functions (Eventarc) on Google Cloud.
Worked example: summarise new CSV files
"""AWS Lambda: when a CSV lands under raw/, count its rows and write a summary under reports/."""
import csv
import io
import json
import urllib.parse
import boto3
s3 = boto3.client("s3")
def lambda_handler(event, context):
results = []
for record in event["Records"]:
bucket = record["s3"]["bucket"]["name"]
key = urllib.parse.unquote_plus(record["s3"]["object"]["key"])
body = s3.get_object(Bucket=bucket, Key=key)["Body"].read().decode("utf-8")
rows = list(csv.DictReader(io.StringIO(body)))
summary = {"source": key, "rows": len(rows), "columns": list(rows[0].keys()) if rows else []}
out_key = "reports/" + key.rsplit("/", 1)[-1].replace(".csv", ".json")
s3.put_object(Bucket=bucket, Key=out_key, Body=json.dumps(summary).encode(), ContentType="application/json")
results.append(summary)
return {"processed": len(results), "results": results}The execution role's policy allows reading only raw/ and writing only reports/ in one bucket, plus writing its own logs:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadRawFiles",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::unimitra-demo-raw-2026/raw/*"
},
{
"Sid": "WriteReports",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::unimitra-demo-raw-2026/reports/*"
},
{
"Sid": "WriteOwnLogs",
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:ap-south-1:*:log-group:/aws/lambda/summarise-csv*"
}
]
}Writing reports to a different prefix from the trigger (raw/) also prevents an infinite loop: if the function wrote back under raw/, each output would trigger it again.
Test it offline with moto:
import boto3
from moto import mock_aws
@mock_aws
def test_summary_written(monkeypatch):
monkeypatch.setenv("AWS_DEFAULT_REGION", "ap-south-1")
s3 = boto3.client("s3", region_name="ap-south-1")
s3.create_bucket(Bucket="demo", CreateBucketConfiguration={"LocationConstraint": "ap-south-1"})
s3.put_object(Bucket="demo", Key="raw/zones.csv", Body=b"LocationID,Borough\n1,EWR\n2,Queens\n")
import handler # import inside the mock so its client is mocked
event = {"Records": [{"s3": {"bucket": {"name": "demo"}, "object": {"key": "raw/zones.csv"}}}]}
result = handler.lambda_handler(event, None)
assert result["results"][0]["rows"] == 2
report = s3.get_object(Bucket="demo", Key="reports/zones.json")["Body"].read()
assert b'"rows": 2' in reportAWS_ACCESS_KEY_ID=testing AWS_SECRET_ACCESS_KEY=testing pytest -q passes. To deploy for real, package the handler, create the role with this policy, and add an S3 event notification for the raw/ prefix (the Infrastructure as Code shelf shows how to do all of it in Terraform).
Common mistakes
"Action": "s3:*", "Resource": "*"because it is quicker. It turns any bug or compromised dependency into full account data access.Writing output where the trigger listens. Recursive invocations can run up a large bill in minutes.
Reading huge files into memory. Lambda has memory and time limits; stream large objects or use a batch service.
Assuming exactly-once delivery. Events can be delivered more than once; make handlers idempotent.
Interview angle
"Design a pipeline that processes files as they arrive." Describe the event trigger, a small idempotent function, a scoped role, a dead-letter queue for failures, and monitoring. Mention the recursion trap and how prefixes avoid it.
Practice task
Add a test for an empty CSV and for a key with spaces (
raw/my file.csv, which arrives URL-encoded).Write the equivalent Azure Function or Cloud Run function trigger configuration.
Tighten the log resource ARN to a specific account id and explain why wildcards in ARNs are risky.