Your weekly dose of actionable cloud wisdom to start the week right
The Problem
Your Azure Functions bill has tripled in three months, but you’re not sure why. You’re on the Consumption plan because “serverless should be cheaper,” yet somehow you’re paying more than you would for dedicated servers. Meanwhile, your functions are still slow during peak times, and you’re stuck wondering if you chose the wrong hosting plan altogether.
The Solution
Master Azure Functions cost optimization by understanding when to use each pricing tier, how to monitor spend effectively, and which optimizations deliver the biggest savings. Most Azure Functions cost problems stem from poor plan selection and lack of monitoring, not the underlying workload.
💡 Pricing Note: All pricing examples use current USD rates as of 2025. Costs vary by Azure region and are subject to change. Always check the official Azure Functions pricing page for the most current rates.
Understanding Azure Functions Pricing Models
1. Consumption Plan Analysis
# bash
# Check your current consumption costs
az monitor metrics list \
--resource "/subscriptions/{subscription-id}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{function-app}" \
--metric "FunctionExecutionCount,FunctionExecutionUnits" \
--start-time 2025-06-01T00:00:00Z \
--end-time 2025-06-15T23:59:59Z
# Current Consumption pricing: $0.000016 per GB-second + $0.20 per million executions
# Free tier: 400,000 GB-s + 1 million executions per month
2. Flex Consumption Plan (Newer Option)
The Flex Consumption plan offers virtual network integration while maintaining serverless billing:
# Python
def calculate_flex_consumption_costs(gb_seconds, executions, always_ready_instances=0):
"""
Calculate Flex Consumption costs with higher base rates but VNet support
"""
# Flex pricing: $0.000026 per GB-s + $0.40 per million executions
# Free tier: 100,000 GB-s + 250,000 executions per month
free_gb_seconds = 100000
free_executions = 250000
billable_gb_seconds = max(0, gb_seconds - free_gb_seconds)
billable_executions = max(0, executions - free_executions)
on_demand_cost = (billable_gb_seconds * 0.000026) + (billable_executions / 1000000 * 0.40)
# Always Ready instances (if configured)
always_ready_cost = 0
if always_ready_instances > 0:
# Baseline: $0.000004 per GB-s when idle
# Execution: $0.000016 per GB-s when active
baseline_hours = 24 * 30 # Monthly hours
always_ready_cost = always_ready_instances * 2.048 * baseline_hours * 0.000004 # 2GB default
return {
"on_demand_cost": on_demand_cost,
"always_ready_cost": always_ready_cost,
"total_monthly_cost": on_demand_cost + always_ready_cost,
"plan_type": "flex_consumption"
}
# Example usage
print(calculate_flex_consumption_costs(gb_seconds=500000, executions=2000000))
3. Premium Plan Cost Calculator
# Python
def calculate_premium_costs(instances, hours_per_month, tier="EP1"):
"""
Premium plan pricing based on vCPU and memory allocation
Note: Pricing varies by region - these are US East estimates
"""
# Current pricing (subject to regional variation):
tier_costs = {
"EP1": {"vcpu_monthly": 116.80, "memory_gb_monthly": 8.322, "vcpu": 1, "memory_gb": 3.5},
"EP2": {"vcpu_monthly": 233.60, "memory_gb_monthly": 16.644, "vcpu": 2, "memory_gb": 7},
"EP3": {"vcpu_monthly": 467.20, "memory_gb_monthly": 33.288, "vcpu": 4, "memory_gb": 14}
}
tier_config = tier_costs[tier]
# Premium plans always have at least 1 instance running
vcpu_cost = instances * tier_config["vcpu"] * tier_config["vcpu_monthly"]
memory_cost = instances * tier_config["memory_gb"] * tier_config["memory_gb_monthly"]
monthly_cost = vcpu_cost + memory_cost
return {
"base_tier": tier,
"instances": instances,
"vcpu_cost": vcpu_cost,
"memory_cost": memory_cost,
"monthly_cost_usd": monthly_cost,
"hourly_cost_usd": monthly_cost / (30 * 24),
"break_even_executions": monthly_cost / 0.0002 # vs Consumption at $0.20/million
}
# Example: When does Premium make sense?
print(calculate_premium_costs(instances=2, tier="EP1"))
4. Dedicated App Service Plan Comparison
# Bash
# When consistent workload makes App Service Plan cheaper
az appservice plan create \
--name function-dedicated-plan \
--resource-group myResourceGroup \
--sku B1 \
--is-linux
# B1 Plan: ~$13.14/month - cheaper than Premium for always-on workloads
# But loses serverless scaling benefits
Cost Optimization Strategies
5. Function Timeout Optimization
# Csharpe
// Reduce execution time = reduce costs
[FunctionName("OptimizedFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
ILogger log)
{
// Set appropriate timeout (default is 5 minutes on Consumption)
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
// Use async/await properly to avoid blocking
var result = await ProcessDataAsync(req, cts.Token);
return new OkObjectResult(result);
}
catch (OperationCanceledException)
{
log.LogWarning("Function timed out after 30 seconds");
return new StatusCodeResult(408); // Request Timeout
}
}
private static async Task<object> ProcessDataAsync(HttpRequest req, CancellationToken cancellationToken)
{
// Efficient processing logic here
// Use streams for large data, avoid loading everything into memory
return new { status = "success" };
}
6. Proper Database Connection Management
# Csharpe
// Create connections per execution (recommended for serverless)
public static class DatabaseHelper
{
private static readonly string _connectionString =
Environment.GetEnvironmentVariable("DatabaseConnectionString");
public static SqlConnection CreateConnection() => new SqlConnection(_connectionString);
// Alternative: Use dependency injection for better lifecycle management
public static async Task<T> ExecuteWithConnectionAsync<T>(Func<SqlConnection, Task<T>> operation)
{
using var connection = CreateConnection();
await connection.OpenAsync();
return await operation(connection);
}
}
[FunctionName("EfficientDataFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
try
{
var result = await DatabaseHelper.ExecuteWithConnectionAsync(async connection =>
{
using var command = new SqlCommand("SELECT * FROM Users", connection);
return await command.ExecuteScalarAsync();
});
return new OkObjectResult(result);
}
catch (Exception ex)
{
return new StatusCodeResult(500);
}
}
7. Batch Processing for Better Economics
# Python
import azure.functions as func
import json
def main(req: func.HttpRequest) -> func.HttpResponse:
# Process multiple items in one execution instead of one-per-execution
try:
batch_data = req.get_json()
if not isinstance(batch_data, list):
return func.HttpResponse("Expected array of items", status_code=400)
# Process up to 100 items per execution (adjust based on timeout)
batch_size = min(len(batch_data), 100)
results = []
for item in batch_data[:batch_size]:
# Process each item
processed_item = process_single_item(item)
results.append(processed_item)
return func.HttpResponse(
json.dumps({
"processed_count": len(results),
"results": results,
"cost_optimization": f"Processed {len(results)} items in single execution"
}),
mimetype="application/json"
)
except Exception as e:
return func.HttpResponse(f"Error: {str(e)}", status_code=500)
def process_single_item(item):
# Your business logic here
return {"id": item.get("id"), "status": "processed"}
Cost Monitoring and Alerting
# Bash
# Set up cost alerts for Azure Functions
az monitor action-group create \
--name "FunctionsCostAlert" \
--resource-group "monitoring-rg" \
--short-name "FuncAlert"
# Create budget alert
az consumption budget create \
--budget-name "azure-functions-monthly" \
--category "Cost" \
--amount 100 \
--time-grain "Monthly" \
--start-date "2025-06-01T00:00:00Z" \
--end-date "2025-12-31T23:59:59Z"
When to Switch Plans
Stay on Consumption If:
- Sporadic workload: Functions run less than 8 hours/day
- Variable demand: Traffic spikes are unpredictable
- Development/testing: Non-production environments
- Low execution count: Less than 1 million executions/month
- Simple networking: No VNet integration needed
Move to Flex Consumption If:
- VNet integration needed: Private networking requirements
- Moderate consistency: Functions running 6-12 hours/day
- Cold start sensitive: User-facing applications requiring faster response
- Regional limitations: Need features not available in traditional Consumption
Move to Premium If:
- Consistent workload: Functions running 12+ hours/day
- Ultra-low latency: Sub-second response time requirements
- Always-on requirement: Pre-warmed instances essential
- Predictable scaling: Known peak usage patterns
- Advanced networking: Complex VNet scenarios
Consider App Service Plan If:
- Always-on requirement: 24/7 background processing
- Cost predictability: Monthly budget needs to be fixed
- Legacy migration: Moving from traditional web apps
- High memory needs: Functions requiring more than 1.5GB consistently
Try This Week
- Audit current costs – Check last month’s Azure Functions spend by plan type
- Analyze execution patterns – Look at execution count and duration trends
- Set up cost alerts – Get notified before bills spiral out of control
- Optimize one function – Pick your most expensive function and apply timeout/batching improvements
- Evaluate storage costs – Review associated storage account charges (not included in free grants)
Enhanced Cost Analysis Script
# Powershell
# PowerShell script to analyze Azure Functions costs with current cmdlets
param(
[string]$SubscriptionId = "your-subscription-id",
[string]$ResourceGroupName = "your-resource-group"
)
# Connect and set context
Set-AzContext -SubscriptionId $SubscriptionId
# Get all function apps in resource group
$functionApps = Get-AzWebApp -ResourceGroupName $ResourceGroupName | Where-Object {$_.Kind -like "*functionapp*"}
foreach ($app in $functionApps) {
Write-Host "Analyzing: $($app.Name)" -ForegroundColor Green
# Get metrics for the last 30 days using current cmdlet
$endTime = Get-Date
$startTime = $endTime.AddDays(-30)
try {
$executionMetrics = Get-AzMetric -ResourceId $app.Id -MetricName "FunctionExecutionCount" -TimeGrain 01:00:00 -StartTime $startTime -EndTime $endTime
$executionUnits = Get-AzMetric -ResourceId $app.Id -MetricName "FunctionExecutionUnits" -TimeGrain 01:00:00 -StartTime $startTime -EndTime $endTime
# Calculate estimated costs
$totalExecutions = ($executionMetrics.Data | Measure-Object -Property Total -Sum).Sum
$totalExecutionUnits = ($executionUnits.Data | Measure-Object -Property Total -Sum).Sum
# Convert MB-milliseconds to GB-seconds
$gbSeconds = $totalExecutionUnits / 1024000
# Calculate costs (after free tier)
$billableExecutions = [Math]::Max(0, $totalExecutions - 1000000)
$billableGbSeconds = [Math]::Max(0, $gbSeconds - 400000)
$executionCost = ($billableExecutions / 1000000) * 0.20
$computeCost = $billableGbSeconds * 0.000016
$estimatedCost = $executionCost + $computeCost
Write-Host " Estimated monthly cost: `$$(estimatedCost.ToString('F2'))"
Write-Host " Total executions: $($totalExecutions.ToString('N0'))"
Write-Host " GB-seconds used: $($gbSeconds.ToString('N0'))"
Write-Host " Plan recommendation: " -NoNewline
# Plan recommendations
if ($estimatedCost -lt 20 -and $totalExecutions -lt 1000000) {
Write-Host "Stay on Consumption" -ForegroundColor Green
} elseif ($estimatedCost -gt 100 -or $totalExecutions -gt 10000000) {
Write-Host "Consider Premium or Dedicated" -ForegroundColor Yellow
} else {
Write-Host "Evaluate Flex Consumption" -ForegroundColor Cyan
}
Write-Host "---"
}
catch {
Write-Warning "Could not retrieve metrics for $($app.Name): $($_.Exception.Message)"
}
}
Common Cost Traps to Avoid
- Default timeouts: 5-minute timeouts on functions that should complete in 30 seconds
- Memory leaks: Functions that don’t dispose of resources properly
- Chatty functions: Making multiple API calls instead of batching
- Wrong plan choice: Premium plan for low-frequency functions
- No monitoring: Flying blind on cost trends
- Storage oversight: Ignoring associated storage account costs
- Regional pricing gaps: Not considering regional cost differences
- Cold start obsession: Paying for Premium when cold starts aren’t actually problematic
Advanced Cost Optimization
Application Insights Optimization
# Csharpe
// Reduce telemetry costs for high-volume functions
[FunctionName("HighVolumeFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
ILogger log)
{
// Configure sampling to reduce Application Insights costs
using (log.BeginScope(new Dictionary<string, object> { ["SamplingRate"] = 0.1 }))
{
// Your function logic here
return new OkObjectResult("Success");
}
}
Circuit Breaker Pattern
# Csharpe
public static class CircuitBreakerHelper
{
private static readonly Dictionary<string, DateTime> _lastFailures = new();
private static readonly TimeSpan _circuitBreakerDuration = TimeSpan.FromMinutes(5);
public static bool IsCircuitOpen(string serviceKey)
{
if (_lastFailures.TryGetValue(serviceKey, out var lastFailure))
{
return DateTime.UtcNow - lastFailure < _circuitBreakerDuration;
}
return false;
}
public static void RecordFailure(string serviceKey)
{
_lastFailures[serviceKey] = DateTime.UtcNow;
}
}
[FunctionName("ResilientFunction")]
public static async Task<IActionResult> Run([HttpTrigger] HttpRequest req, ILogger log)
{
const string serviceKey = "external-api";
if (CircuitBreakerHelper.IsCircuitOpen(serviceKey))
{
log.LogWarning("Circuit breaker open for {ServiceKey}", serviceKey);
return new StatusCodeResult(503); // Service Unavailable
}
try
{
// Call external service
var result = await CallExternalServiceAsync();
return new OkObjectResult(result);
}
catch (Exception ex)
{
CircuitBreakerHelper.RecordFailure(serviceKey);
log.LogError(ex, "Service call failed for {ServiceKey}", serviceKey);
return new StatusCodeResult(503);
}
}
Storage Cost Considerations
Don’t forget that Azure Functions require storage accounts, which aren’t included in the free grants:
- Standard storage rates apply for function app files and logs
- Networking costs for cross-region data transfer
- Application Insights data retention costs
- Deployment packages stored in blob storage
Regional Pricing Impact
Function costs vary significantly by Azure region:
- US East/West: Typically lowest rates (baseline pricing)
- Europe: 10-20% premium over US regions
- Asia Pacific: 15-25% premium
- Government clouds: Different pricing structure entirely
Pro Tip: Consider deploying functions in cost-optimized regions if latency requirements allow.
Pro Tips
- Free tier maximization: Azure Functions includes 1 million free executions and 400,000 GB-seconds per month per subscription. Design your architecture to maximize free tier usage across multiple function apps.
- Flex vs Traditional Consumption: If you need VNet integration, Flex Consumption often costs less than Premium for moderate workloads, despite higher per-execution rates.
- Memory right-sizing: Functions are billed in 128MB increments. A function using 129MB pays for 256MB. Optimize memory usage to stay within boundaries.
- Execution time minimization: Every millisecond counts. Profile your functions and eliminate unnecessary delays.
- Deployment package optimization: Smaller deployment packages reduce cold start times and storage costs.
Discovered a clever Azure Functions cost optimization? I’d love to hear about it – real-world savings stories make the best Monday tips!








