Idle Lambda functions are different from over-provisioned Lambda memory: over-provisioned findings apply to functions that are being invoked but allocated more memory than they need. Idle functions have zero invocations in the last 30 days.
Before you delete
- Confirm no EventBridge rules, API Gateway routes, SQS queues, or Step Functions workflows still reference the function.
- Run
aws lambda list-event-source-mappings --function-name <name>and remove any active mappings. - Check for aliases or weighted routing that might still point at this function.
- Remember that the CloudWatch log group
/aws/lambda/<name>is not deleted with the function — delete or set retention on it separately if you want to stop log storage charges.
What triggers this finding
An active Lambda function with zero invocations over the last 30 days (from CloudWatch Invocations metrics), where the function was last modified more than 30 days ago — recently deployed functions waiting for a trigger are excluded.
Typical fix
Delete the function if it is no longer used. Remove event source mappings, EventBridge rules, API Gateway integrations, and aliases first. On paid plans, Parsivex generates a delete-function script with pre-checks — see Remediation script safety.
Example savings
Direct Lambda compute cost is $0/month for zero-invocation functions. Parsivex shows a conservative nominal estimate (typically ~$0.50/month) for possible CloudWatch Logs storage in /aws/lambda/{function} — actual savings may be lower.
See also: Severity and savings estimates for how Parsivex calculates figures on your report.
Where an idle function's cost actually sits
Lambda bills requests and GB-seconds. A function with no invocations generates neither, so its direct compute cost is genuinely $0 a month — and Parsivex says so rather than inventing a number. The savings figure on this finding is a nominal $0.50 placeholder, or a storage-derived figure when the size of the function's log group is known, priced at the same $0.03 per GB-month that CloudWatch charges for archival storage.
The costs that survive an idle function are the ones attached to it rather than to its execution:
- The log group.
/aws/lambda/<name>outlives the function and keeps billing storage. If it was never given a retention policy it will also appear as a separate CloudWatch Logs finding. - Provisioned concurrency. This is billed hourly for as long as it is configured, invocations or not, and it is easily the most expensive thing that can hide behind a zero-invocation function. Parsivex does not price it, so check it yourself before dismissing the finding as trivial.
- Reserved concurrency. It costs nothing directly, but it carves a permanent slice out of the account's concurrency pool that no other function can use.
Verifying zero invocations yourself
The metric Parsivex reads is Invocations, summed over 30 daily datapoints:
aws cloudwatch get-metric-statistics --namespace AWS/Lambda \
--metric-name Invocations \
--dimensions Name=FunctionName,Value=report-generator \
--start-time 2026-07-12T00:00:00Z --end-time 2026-08-11T00:00:00Z \
--period 86400 --statistics Sum
An empty Datapoints array is the expected result for a truly idle function — CloudWatch only publishes the metric when the function runs. If the CloudWatch call fails for a function, Parsivex skips it entirely rather than treating the missing data as zero.
Then check the two things the metric cannot tell you:
aws lambda list-provisioned-concurrency-configs --function-name report-generator
aws lambda get-function-configuration --function-name report-generator \
--query '{LastModified:LastModified,Memory:MemorySize,VpcConfig:VpcConfig.SubnetIds}'
The 30-day age gate uses LastModified, which is why a freshly deployed function waiting on its first trigger is never flagged.
Functions that look idle but are not
- Lambda@Edge. Invocation metrics are published in the region where the request was served, not the region where the function is defined. An edge function serving millions of requests worldwide reports nothing against its home region and looks completely dead.
- Disaster recovery and runbook functions. Failover handlers, break-glass access grants, and incident automation are supposed to have zero invocations. Their value is measured on the day they run.
- Infrequent schedules. A quarterly reconciliation job or an annual certificate rotation cannot show up in a 30-day window. Check the EventBridge rule's schedule expression before concluding anything.
- Deploy-time and CloudFormation custom resources. These run during stack operations and can sit untouched for months between releases.
Note the inverse case too: any redeployment updates LastModified and resets the 30-day clock, so a genuinely unused function that a pipeline keeps redeploying will stay invisible to this detector.
Risk and reversibility of deleting
Deleting a function deletes the code. If the function is not in version control or managed by CDK, Terraform, or SAM, the source is gone — Lambda has no recycle bin. If it is managed by infrastructure as code, deleting by hand only creates drift: the next deploy recreates it, and the finding returns next scan. Remove it from the stack instead.
The safe sequence is to disable before deleting. Setting reserved concurrency to zero stops all invocations without destroying anything, and it is a one-command change in either direction:
aws lambda put-function-concurrency --function-name report-generator --reserved-concurrent-executions 0
Anything that still tries to invoke the function now fails loudly and traceably, which is exactly the signal you want. Leave it that way for a full business cycle — a month covers most schedules, a quarter covers the rest — and delete only once nothing has complained. Remember to remove the reserved concurrency limit rather than leaving a zeroed configuration behind if you decide to keep the function.