Skip to content
Azure Functions Guide — Triggers, Bindings, Durable Functions, and Python Examples

Azure Functions Guide — Triggers, Bindings, Durable Functions, and Python Examples

DodaTech Updated Jun 15, 2026 8 min read

Azure Functions is Microsoft’s serverless compute platform that lets you run event-driven code without managing infrastructure, with built-in bindings for seamless Azure service integration.

What You’ll Learn

By the end of this tutorial, you’ll understand Azure Functions triggers and bindings, the differences between consumption and premium plans, Durable Functions for stateful workflows, and how to build HTTP-triggered functions in Python.

Why Azure Functions Matters

Azure Functions provides deep integration with the Microsoft ecosystem — Office 365, Dynamics, Teams, and Azure services. If your organization uses Microsoft tools, Azure Functions enables automation and event processing without leaving the stack. DodaTech uses Azure Functions for processing DodaZIP license validation requests and webhook event processing.

Azure Functions Learning Path


flowchart LR
  A[Cloud Basics] --> B[Azure]
  B --> C[Azure Functions]
  C --> D{You Are Here}
  D --> E[Triggers]
  D --> F[Bindings]
  D --> G[Durable Functions]
  E --> H[HTTP]
  E --> I[Blob Storage]
  E --> J[Queue Storage]

Prerequisites: Python basics. Understanding of Azure services and serverless concepts helps.

What Are Azure Functions?

Think of Azure Functions like a mailroom in a large office building. When a package arrives (trigger), the mailroom processes it automatically — sorting, logging, routing (bindings). You don’t specify which mailroom worker does the job or when they work. The system handles staffing (scaling) automatically.

If a package needs multiple steps? Durable Functions chains them together: receive → log → sort → deliver. One step can wait for the previous without managing state manually.

Triggers and Bindings

Triggers start function execution. Bindings connect to data declaratively.

TriggerWhen It FiresUse Case
HTTPHTTP request receivedREST API endpoints, webhooks
TimerScheduled intervalBatch processing, health checks
Blob StorageBlob created/updatedImage processing, file ingestion
Queue StorageMessage added to queueBackground job processing
Cosmos DBDocument changedReal-time data sync
Event GridAzure service eventMulti-service orchestration
Service BusQueue/topic messageEnterprise messaging
# http_trigger.py
# Azure Function with HTTP trigger + input/output bindings
import azure.functions as func
import json
import logging
from datetime import datetime

def main(req: func.HttpRequest,
         inputblob: func.InputStream,
         outputblob: func.Out[str]) -> func.HttpResponse:
    """
    HTTP-triggered function that:
    - Accepts a name in the request body
    - Reads a template from Blob Storage (input binding)
    - Writes a personalized message (output binding)
    - Returns the result as HTTP response
    """
    logging.info('Python HTTP trigger function processed a request.')

    # Get name from request
    try:
        req_body = req.get_json()
        name = req_body.get('name', 'World')
    except ValueError:
        name = req.params.get('name', 'World')

    # Read template from blob (input binding)
    template = "Hello, {name}! This message was generated at {time}."
    if inputblob:
        template = inputblob.read().decode('utf-8')

    # Generate message
    message = template.format(name=name, time=datetime.now().isoformat())

    # Write to blob (output binding)
    outputblob.set(message)

    logging.info(f'Generated message for {name}')

    return func.HttpResponse(
        json.dumps({"message": message, "status": "success"}),
        mimetype="application/json",
        status_code=200
    )
# functions_simulator.py
# Simulate Azure Functions locally for learning
import time
import json
from datetime import datetime
from collections import defaultdict

class FunctionRuntime:
    def __init__(self, plan="Consumption", memory_mb=128):
        self.plan = plan
        self.memory = memory_mb
        self.stats = defaultdict(int)

    def http_trigger(self, method, path, body=None):
        """Simulate an HTTP-triggered function."""
        self.stats["total_invocations"] += 1
        start = time.time()

        # Simulate cold start
        if self.stats["total_invocations"] == 1:
            penalty = 0.3 if self.plan == "Consumption" else 0.05
            time.sleep(penalty)

        # Process request
        name = "World"
        if body:
            try:
                data = json.loads(body)
                name = data.get("name", name)
            except json.JSONDecodeError:
                pass

        result = {
            "message": f"Hello, {name}! Processed at {datetime.now().isoformat()}",
            "method": method,
            "path": path,
            "plan": self.plan,
        }

        duration = (time.time() - start) * 1000
        self.stats["total_duration_ms"] += duration

        print(f"[{self.plan}] HTTP {method} {path} → '{result['message']}' ({duration:.0f}ms)")
        return result

    def blob_trigger(self, container, blob_name):
        """Simulate a Blob Storage-triggered function."""
        self.stats["total_invocations"] += 1
        self.stats["blob_processed"] += 1
        print(f"[BlobTrigger] Processing {container}/{blob_name}...")
        time.sleep(0.1)
        print(f"[BlobTrigger] ✓ Resized thumbnail for {blob_name}")

    def stats_report(self):
        avg_duration = self.stats["total_duration_ms"] / max(self.stats["total_invocations"], 1)
        print(f"\n=== Function Runtime Stats ===")
        print(f"  Plan: {self.plan}")
        print(f"  Total invocations: {self.stats['total_invocations']}")
        print(f"  Average duration: {avg_duration:.0f}ms")
        print(f"  Blobs processed: {self.stats.get('blob_processed', 0)}")

# Demo
print("=== Azure Functions Simulation ===\n")

runtime = FunctionRuntime(plan="Consumption")
runtime.http_trigger("GET", "/api/hello")
runtime.http_trigger("POST", "/api/hello", '{"name": "DodaTech"}')
runtime.http_trigger("GET", "/api/health")
runtime.blob_trigger("uploads", "photos/selfie.jpg")
runtime.blob_trigger("uploads", "docs/report.pdf")
runtime.stats_report()

Expected output:

=== Azure Functions Simulation ===

[Consumption] HTTP GET /api/hello → 'Hello, World! Processed at ...' (420ms)
[Consumption] HTTP POST /api/hello → 'Hello, DodaTech! Processed at ...' (5ms)
[Consumption] HTTP GET /api/health → 'Hello, World! Processed at ...' (3ms)
[BlobTrigger] Processing uploads/photos/selfie.jpg...
[BlobTrigger] ✓ Resized thumbnail for selfie.jpg
[BlobTrigger] Processing uploads/docs/report.pdf...
[BlobTrigger] ✓ Resized thumbnail for report.pdf

=== Function Runtime Stats ===
  Plan: Consumption
  Total invocations: 5
  Average duration: 86ms
  Blobs processed: 2

Consumption vs Premium Plan

ConsumptionPremium (EP)Dedicated (App Service)
Cold startHigher (up to 10s)Lower (always warm)None (always on)
ScalingAuto, up to 200 instancesAuto, up to 100Manual scale
Timeout5 min (default), 10 min (max)30 min (default), unlimited (Linux)Unlimited
VNET integrationNoYesYes
Always readyNoYes (configurable min)Yes
PricingPay per executionPay per GB-second + basePay per VM size

Durable Functions

Durable Functions extend Azure Functions with stateful orchestration.

# durable_demo.py
# Simulate Durable Functions chaining pattern
import time
import json
from datetime import datetime

class DurableOrchestrator:
    def __init__(self):
        self.history = []

    def call_activity(self, name, input_data):
        """Simulate calling an activity function."""
        start = time.time()
        time.sleep(0.3)  # Simulate work
        result = f"{name}({input_data}) → processed at {datetime.now().strftime('%H:%M:%S')}"
        self.history.append({"activity": name, "input": input_data, "result": result, "duration": round((time.time()-start)*1000)})
        return result

    def orchestrate(self):
        """Simulate a Durable Functions orchestration."""
        print("🏁 Orchestration started\n")

        # Step 1: Validate order
        order = {"order_id": "ORD-2026-001", "amount": 150.00, "customer": "dodatech"}
        step1 = self.call_activity("ValidateOrder", order)
        print(f"  Step 1: {step1}")

        # Step 2: Process payment
        step2 = self.call_activity("ProcessPayment", {"order_id": order["order_id"], "amount": order["amount"]})
        print(f"  Step 2: {step2}")

        # Step 3: Send confirmation (fan-out)
        confirmations = ["SendEmail", "SendSMS", "UpdateCRM"]
        for activity in confirmations:
            result = self.call_activity(activity, {"order_id": order["order_id"]})
            print(f"  Step 3: {result}")

        print(f"\n🏁 Orchestration completed")
        return {"status": "completed", "steps": len(self.history), "history": self.history}

orch = DurableOrchestrator()
result = orch.orchestrate()

print(f"\n=== Orchestration Summary ===")
for step in result["history"]:
    print(f"  {step['activity']:<20} {step['duration']:>4}ms")
print(f"\nTotal steps: {result['steps']}")

Expected output:

🏁 Orchestration started

  Step 1: ValidateOrder({'order_id': 'ORD-2026-001', ...}) → processed at 10:00:01
  Step 2: ProcessPayment({'order_id': 'ORD-2026-001', ...}) → processed at 10:00:01
  Step 3: SendEmail({'order_id': 'ORD-2026-001'}) → processed at 10:00:01
  Step 3: SendSMS({'order_id': 'ORD-2026-001'}) → processed at 10:00:02
  Step 3: UpdateCRM({'order_id': 'ORD-2026-001'}) → processed at 10:00:02

🏁 Orchestration completed

=== Orchestration Summary ===
  ValidateOrder          300ms
  ProcessPayment         300ms
  SendEmail              301ms
  SendSMS                300ms
  UpdateCRM              301ms

Total steps: 5

Common Azure Functions Mistakes

1. Not Handling Cold Starts for Production APIs

Consumption plan cold starts can exceed 10 seconds for C#/.NET. Use Premium plan with “always ready” instances for latency-sensitive HTTP APIs.

2. Writing to Blob Inside Function Instead of Using Output Bindings

Using the SDK to write to Blob is verbose and error-prone. Declare [Blob("output/{name}.txt")] as an output binding — the runtime handles it.

3. Overloading the 5-Minute Timeout

Consumption plan functions timeout after 5 minutes (default). Long-running operations should use Durable Functions or Premium plan with extended timeout.

4. Ignoring Logging and Application Insights

Azure Functions automatically integrates with Application Insights for monitoring. Without it, debugging failures is extremely difficult.

5. Tight Coupling Between Functions

Each function should be independent. Use Queue Storage or Event Grid for loose coupling between functions — function A writes to queue, function B reads from it.

Practice Questions

1. What is the difference between Azure Functions Consumption and Premium plans?

Consumption scales automatically but has cold starts and 5-min timeout. Premium has always-warm instances, VNET integration, unlimited timeout, and predictable pricing with base cost.

2. What are bindings in Azure Functions?

Bindings are declarative connections to Azure services. Input bindings read data into your function; output bindings write data from your function. They eliminate SDK boilerplate.

3. What are Durable Functions used for?

Durable Functions enable stateful orchestration of serverless workflows — chaining activities, fan-out/fan-in, human interaction patterns, and monitoring. They manage state and checkpoints automatically.

4. How do you connect Azure Functions to other Azure services?

Through triggers (start execution: HTTP, Blob, Queue, Timer) and bindings (read/write data: Blob, Cosmos DB, SQL, SendGrid). Also via Event Grid for event routing.

5. Challenge: Design a serverless order processing system using Azure Functions with payment validation, inventory check, shipping, and notification.

Durable Functions orchestration: HTTP trigger to start order → ValidatePayment activity → CheckInventory activity → parallel ReserveInventory + ChargePaymentCreateShipmentSendConfirmation. Use Queue Storage between services for loose coupling.

Mini Project: Multi-Step Azure Function Workflow

# azure_workflow.py
# Simulate a multi-step Azure Functions workflow
import time
import json

class Workflow:
    def __init__(self):
        self.steps = []
        self.context = {}

    def add_step(self, name, func, depends_on=None):
        self.steps.append({"name": name, "func": func, "depends_on": depends_on or []})

    def run(self, initial_input):
        results = {}
        for step in self.steps:
            all_deps_ready = all(dep in results for dep in step["depends_on"])
            if not all_deps_ready:
                print(f"  ⏳ {step['name']}: waiting for dependencies")
                continue
            print(f"  ▶ {step['name']}: executing...")
            output = step["func"]({"input": initial_input, "context": results})
            results[step["name"]] = output
            print(f"    ✓ {output}")
        return results

wf = Workflow()
wf.add_step("ValidateOrder", lambda ctx: "Order validated")
wf.add_step("ProcessPayment", lambda ctx: f"Payment of ${ctx['input']['amount']} processed", depends_on=["ValidateOrder"])
wf.add_step("UpdateInventory", lambda ctx: "Inventory updated", depends_on=["ProcessPayment"])
wf.add_step("SendNotification", lambda ctx: "Email + SMS sent", depends_on=["UpdateInventory"])
wf.add_step("AuditLog", lambda ctx: "Audit logged", depends_on=["ProcessPayment", "UpdateInventory"])

print("=== Azure Functions Workflow ===\n")
wf.run({"order_id": "ORD-001", "amount": 129.99})

print("\n✓ Workflow complete")

Expected output:

=== Azure Functions Workflow ===

  ▶ ValidateOrder: executing...
    ✓ Order validated
  ▶ ProcessPayment: executing...
    ✓ Payment of $129.99 processed
  ▶ UpdateInventory: executing...
    ✓ Inventory updated
  ▶ SendNotification: executing...
    ✓ Email + SMS sent
  ▶ AuditLog: executing...
    ✓ Audit logged

✓ Workflow complete

Related Concepts

What’s Next

You now understand Azure Functions! Next, compare with AWS Lambda and Google Cloud Run, and explore cloud monitoring for observability.

  • Practice daily — Create an HTTP-triggered function in the Azure Portal
  • Build a project — Build a serverless image resizer with Blob trigger
  • Explore related topics — Check out Azure Logic Apps for no-code workflows

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro