Create a Function App, add a timer-triggered function, deploy it, and watch it fire on schedule and scale to zero between runs.
Code that isn't running most of the time
Class Forty ended with a promise: a worker would drain the order-events queue. The obvious way to build that worker is a program that runs forever — a virtual machine or an App Service that sits in a loop, polling the queue, waiting for something to do. It works, and it is also the January-trough problem from Class One in a new costume: the worker bills for twenty-four hours a day to do the few minutes of real work that actually arrive, and ninety-nine percent of what you pay for is a process staying awake in case a message shows up. You already know Azure's answer to "billed for existing, not for working," because Class Twenty-Seven's Container Apps job scaled to zero. Azure Functions is that idea taken to its smallest unit: a single piece of code that does not run — does not exist as a live process — until an event wakes it, runs for as long as the work takes, and then disappears.
The shift is in what you are responsible for. With a VM you own the machine, the operating system, the runtime, the scaling, and the polling loop, and you rent the code some space inside all of that. With a Function you own the code and nothing else: you write a handler that says "when an order message arrives, do this," and Azure owns the host it runs on, the act of watching the queue, the decision to start one copy or fifty when a backlog lands, and the decision to stop them all when it clears. This is what "serverless" actually names — not the absence of servers, but the absence of servers you manage. The servers are Microsoft's problem; the function is yours.
Two workloads from earlier in the bootcamp are Functions waiting to be recognised. The nightly price import from Class Eleven — run at 02:00, work for a few minutes, cost nothing the other twenty-three hours and fifty-odd minutes — is a function on a timer. The order-events consumer from Class Forty — wake on each message, fulfil the order, go back to sleep — is a function on a queue trigger. Neither wants a server standing by; both want code that appears when there is a reason and vanishes when there is not.
You bring the function; Azure brings everything around it.
Triggers and bindings — the whole programming model
A Function is three things, and once you can name them the product stops being mysterious: a trigger, the code, and its bindings. The trigger is the one event that wakes the function, and there is exactly one per function. The bindings are the declarative connections to the data the function reads and writes — zero or more of them — so the plumbing to Azure services becomes configuration instead of SDK boilerplate you hand-write and maintain.
- Trigger & binding
- A trigger is the single event that starts a function — a queue or Service Bus message, an HTTP request, a timer/cron schedule, a new blob, an Event Grid event. A binding is a declarative input or output connection — read this blob, write to that queue — that hands the function its data without connection code.
The triggers are the map of what Functions is for. A queue or Service Bus trigger makes a function the consumer at the end of Class Forty's queue — the platform does the peek-lock, hands your code the message, and completes or dead-letters it based on whether you threw. A timer trigger is cron in the cloud, which is the price import. An HTTP trigger makes a function a tiny API endpoint — a webhook receiver, a lightweight backend — often sat behind the Class Thirty-Eight gateway. A blob or Event Grid trigger reacts to something happening in the estate: an image uploaded, a resource created. The shape is always the same — something happens, a function wakes, it runs. Bindings then spare you the glue: an output binding that writes the result to a "fulfilled" queue is one line of configuration, not a page of client setup, and the senior habit is to let the binding carry the plumbing and keep your code about the work.
The hosting plan is the decision that bites
The code is the easy part; the plan you run it on is the choice that shows up in the bill and the incident channel, and it is the §3 skill. Three answers matter.
| Question | Consumption (classic) | Flex Consumption | Premium | App Service plan |
|---|---|---|---|---|
| Scales to zero? | Yes — pay only per run | Yes — pay only per run | No — keeps warm instances | No — always on |
| Cold starts? | Yes — first call after idle is slow | Yes, but fast — and paid always-ready instances remove them | No — pre-warmed | No |
| VNet / private endpoints? | No | Yes | Yes | Yes |
| Max run per execution | 5 min default, 10 max | 30 min default, extendable | Long / unbounded | Unbounded |
| Best for | Existing apps; the legacy incumbent | New serverless work — the recommended default | Latency-critical, heavy, VNet-bound | You already run an App Service plan |
Read the table as one question — what can you tolerate? The serverless idea comes in two generations. Classic Consumption is the incumbent: it scales from zero to many and bills per execution and gigabyte-second, and its two prices are a cold start — the first request after the function has gone idle waits while Azure spins a host, which a background queue drain never notices and a user-facing HTTP endpoint very much does — and a five-to-ten-minute execution ceiling that quietly rules out long jobs. Flex Consumption is where Microsoft now points new work: it keeps the whole serverless bargain — scale to zero, pay per run — while starting faster from cold, running to thirty minutes by default, and reaching into VNets; if a cold start is genuinely unaffordable, always-ready instances remove it for a fee. Premium stops pretending to be serverless: warm instances all the time, no cold start ever, at a standing floor cost. The App Service plan is the answer when you already run one and have spare capacity — the functions ride along, always on, no cold start, no scale-to-zero saving. The decision rule is the Container-Apps rule again: start on Flex Consumption, and move only when a named requirement — zero cold start at any price, an unbounded run, an existing plan with room — forces you.1
The three rules that keep a function honest
Functions are unforgiving of a few assumptions carried over from long-running code, and every one of the mistakes is an interview question. First, a function is stateless: it keeps nothing in memory between runs, because the instance that handled the last message may not be the one that handles the next, or may not exist at all. Anything that must persist — a running total, a cursor, a half-finished workflow — lives in storage, a queue, or a database, never in a variable. A function that "remembers" is a function that works in testing and fails the moment it scales past one instance.
Second, a function must be idempotent, for the exact reason Class Forty drilled: queue and Service Bus triggers deliver at-least-once, so your function will occasionally run twice on the same message — a redelivery after a host recycle, a lock that lapsed mid-run. Keying on the order id and checking before acting is not optional hygiene here; it is the difference between a redelivery being invisible and a customer being charged twice. The trigger's at-least-once contract and the function's idempotency are two halves of one design.
Third, keep a function short and single-purpose. The ten-minute limit is a hint about intent, not just a cap: a function should do one unit of work and end. Chaining a long synchronous sequence inside one function — charge, then wait for the warehouse, then wait for the courier, then email — fights both the time limit and the stateless model, and when it fails halfway you cannot tell what already happened. Work that spans steps, waits, or fan-out wants orchestration, which is §5.2
One function, one job, no memory.
Durable Functions — the phrase for workflows
The moment a job stops being "handle this one message" and becomes "run these five steps, some of which wait," a plain function's statelessness and ten-minute limit turn from features into walls. The answer is Durable Functions, an extension that adds a stateful orchestrator: a function that describes a workflow in ordinary code — call this, await the result, call that, wait for a human to approve, fan out to a hundred parallel tasks and fan back in — while the runtime checkpoints its progress to storage after every step and replays it to survive restarts, timeouts, and the stateless model underneath.
- Durable Functions
- An extension to Azure Functions for stateful workflows: an orchestrator function coordinates multiple activity functions — chaining, fan-out/fan-in, waiting for external events or human approval, long-running timers — with the runtime persisting and replaying state so a multi-step process survives restarts and outlives any single execution.
You do not need it for the order-events consumer, which is one message and one job. You reach for it when fulfilment becomes a real workflow — charge the card, and only if that succeeds reserve the stock, then wait for the warehouse to confirm, then email the customer — because that sequence has ordering, failure branches, and a wait no single stateless run should hold open. Naming Durable Functions when the interviewer describes a multi-step process is the signal that you know where a plain function stops; the pattern also outlives Azure, because "orchestrator plus activities plus checkpointed state" is how every serverless workflow engine, from Step Functions to Temporal, is shaped.3
The worker Class Forty promised, finally built
The consumer at the end of Campux's orders topic is a Function App on the Consumption plan, and it is almost boringly small: one function, a Service Bus trigger on the fulfilment subscription, and a handler that is idempotent on the order id from its first line — the discipline Class Forty demanded, now load-bearing. Each message wakes the function; it charges, reserves, and emails; it completes the message, or throws and lets Service Bus dead-letter the poison after three tries. When the Black Friday backlog lands, Azure scales the function from zero to dozens of instances to drain it, then back to zero when the queue empties — and the bill for the quiet overnight hours, when nothing runs, is nothing. The nightly price import moves here too, as a second function on a 02:00 timer trigger, and the last always-on VM in the estate is switched off.
Then fulfilment grows the way real fulfilment does. Finance wants the charge to happen before the stock reservation, the warehouse confirmation to be waited on, and the customer email sent only after all of it succeeds — with a clean record, at any moment, of exactly which step an order reached. That is no longer one function's job; a single stateless run cannot hold a warehouse wait open, and the ten-minute limit forbids it trying. So fulfilment becomes a Durable Functions orchestration: an orchestrator chains charge → reserve → await-warehouse → email as activity functions, checkpointing after each, surviving a host recycle mid-wait without losing its place. Six functions, no servers, a workflow you can point at and read — and outside the two annual peaks, the compute bill for the whole of order fulfilment rounds to zero. The checkout from Class Forty still does its one thing in a blink; the work behind it now wakes only when there is work, which is the whole argument of the bootcamp, kept.
The official pages, and a CAMPUX overview
Azure Functions — overview (triggers, bindings, the model)
learn.microsoft.com/azure/azure-functions/functions-overview
Azure Functions hosting options — Consumption, Flex, Premium, Dedicated
learn.microsoft.com/azure/azure-functions/functions-scale
A Function App created on Consumption, a timer-triggered function firing once a minute, the invocation list as the bill, and a queue trigger draining order-events — will live here. Video to be added.
A Function App, a timer trigger, and a run that costs nothing between runs
Create the smallest real Function App, give it a timer-triggered function, and watch it fire on schedule and bill for the seconds it runs.
A Function App needs a storage account for its own bookkeeping; create both on the Consumption plan:
az group create --name rg-func-lab --location eastus az storage account create -n stfnlab<initials> -g rg-func-lab --sku Standard_LRS az functionapp create -n fn-campux-<initials> -g rg-func-lab \ --consumption-plan-location eastus \ --runtime node --functions-version 4 \ --storage-account stfnlab<initials>
What to notice: --consumption-plan-location is the whole §3 decision made in one flag — scale to zero, pay per run, accept cold starts. The storage account is not optional: Functions keeps its trigger state and logs there, which is also where Durable Functions checkpoints workflows.In the portal: open the Function App → Create function → Timer trigger. Set the schedule to every minute with the CRON expression 0 */1 * * * * and create it.
What to notice: the timer trigger is cron in the cloud — the Class Eleven price import with no server to keep it running. One function, one trigger, one job. The six-field CRON includes seconds, which trips people who bring five-field habits from Linux.Open the function → Monitor (or Logs) and watch it fire once a minute, each run a few milliseconds of billed time. Between runs, nothing is running and nothing is billed.
What to notice: the invocation list is the bill — each row is a run you paid for, and the gaps between them cost nothing. This is the trough from Class One finally priced correctly: the function exists only when there is a reason for it to.Tear it down so the timer stops and nothing lingers:
az group delete --name rg-func-lab --yes --no-wait
The lesson: you deployed compute that costs money only while it works. Swap the timer for a queue trigger and you have Class Forty's consumer; swap it for an HTTP trigger and you have a tiny API — same model, different first line.
Zoom out: cheap-per-run is not free, and scale-to-zero has a first customer
A function that costs nothing at rest feels like it removed a constraint. It moved one. Reason about what event-driven, scale-to-zero compute does to the system around it before the exam.
A queue backs up, so Functions scales out to dozens of instances to drain it — and all of them hit the same database at once, which slows, which makes each function run longer, which makes Functions scale out further. Elastic compute in front of a fixed backend is a stampede waiting for a trigger. What limits the concurrency, and where does the backpressure live?
Scale-to-zero means the first request after idle pays the cold start. A background drain never notices; a customer-facing HTTP function does, and now your latency depends on how recently someone else called it. You traded a standing cost for a variable one — who feels the variance, and is it the person you least want to?
Functions scales the compute effortlessly, so the ceiling moves to whatever it calls — the database connections, the downstream API's rate limit, the Service Bus throughput. The wall is no longer your worker; it is the least elastic thing behind it. Adding function instances past that point just queues the contention somewhere less visible.
Per-execution billing makes cost scale with traffic, which is wonderful until a retry storm or a recursive trigger — a function that writes to the queue that triggers it — turns a bug into a runaway invoice. Cheap-per-run removed the standing cost and added a new failure mode: the loop that bills. What caps it?
The ten-minute limit and statelessness are invisible at one message a minute and fatal at ten thousand a second with a step that waits. What breaks first as volume climbs — the execution limit, the downstream, or the assumption that one function could hold the whole workflow? Which is a plan change and which is Durable Functions?
The engineer who ships is asked "does it run?" The engineer who gets promoted is asked "and what does it stampede when it does?" — and has already capped the concurrency.
Turning a standing cost into a per-run one
A virtual machine exists to run one nightly job and one queue drain, and it bills twenty-four hours a day to do it. You rewrite the two workloads as Functions — a timer trigger and a queue trigger — switch the VM off, and the compute bill for that work collapses to the seconds it actually runs. Same jobs, same output, a line item that finally matches the work — and you are the one who stopped paying for idle.
Examination
Four drills, then two situations. The situations have no marking scheme — write your answer before you reveal the reasoning, or the exercise is worthless. Nothing is stored; this is between you and the page.
B — the requirement names three things Consumption cannot give, so it disqualifies the default on its own terms. "Under 200 ms every time" collides with cold starts, which make the first call after idle slow and unpredictable — exactly the person you least want to hit them. "Private endpoint" needs VNet integration, which Consumption limits. And "twelve minutes" exceeds Consumption's roughly ten-minute cap. C is the trap that reads plausible: you cannot simply raise Consumption past its ceiling — the limit is a property of the plan, not a slider — and a candidate who thinks it is a config knob has revealed they have not hit the wall. A ignores every stated constraint to reach for cheapest. D is false on both counts the question was built to test. Premium is not the reflex — it costs more, and you name each requirement that forced you up from Consumption, which is the §3 discipline.
B — a function has exactly one trigger, and everything else it touches is a binding. The one event that wakes the function is the Service Bus message; the database read is an input binding and the queue write is an output binding — declarative connections that hand the function its data without connection code. A is the classic confusion: only the thing that starts the function is a trigger, and there is always precisely one. D repeats the error more subtly — a database read does not start the function, so it cannot be a trigger. C is true only if you refuse the model: you can hand-code with SDKs, but bindings exist so you do not, and choosing boilerplate over a binding is choosing to maintain plumbing the platform offered to carry. "One trigger, zero-or-more bindings" is the sentence to have ready.
Stateless, idempotent, and time-capped — the three constraints a function is written around. Each changes your code: state goes to storage not variables, the handler checks a natural key before acting, and the work is kept short enough to finish. The two rejects are the mistakes those constraints exist to prevent. "A single function holds a multi-hour workflow open" is exactly what Durable Functions exists because you cannot do — a plain function is stateless and time-capped, so a long wait belongs in an orchestrator, not one run. And "keeps an instance warm at all times" describes Premium, not the Consumption default the question implies; asserting no-cold-starts as a universal truth of Functions is the misconception §3 corrects. The three trues are the design brief; the two falses are the §4 and §5 walls.
# design: fulfilment function (Consumption)
1. Service Bus trigger on the fulfilment subscription;
handler is idempotent on the order id.
2. Keeps a running count of orders processed in a
module-level variable, logged each run.
3. On failure it throws, so Service Bus retries and
eventually dead-letters per Class 40.
4. Long multi-step fulfilment is moved to a Durable
Functions orchestration.
Line two — the one line that quietly assumes a server. A module-level variable holding a running count works perfectly in testing, where one instance handles every message in sequence, and fails silently in production, where Functions scales to many instances that share no memory and any of which may be recycled between runs. The "count" becomes per-instance, resets without warning, and means nothing — a metric that lies. Persistent state belongs in storage, a database, or a metrics backend (Class Thirty's App Insights), never in a variable that outlives a single execution only by accident. The distractors are the design working: idempotency on a Service Bus trigger is not just possible but required (A inverts §4); throwing to drive retry-then-dead-letter is precisely the Class Forty contract (C); and moving multi-step work to Durable Functions is §5's correct call, not overkill (D). The reviewer's reflex: in serverless code, any state that is not written down is a bug that waits for the second instance.
Concede that the fix works, then question what it teaches you to stop noticing. Moving to an App Service plan (or Premium) genuinely removes the ten-minute cap, and for some workloads that is the right answer — so do not reject it reflexively. But the timeout was not an arbitrary obstacle; it was the platform telling you the work does not fit the "wake, do one short thing, end" shape a single function is built for. Lifting the limit lets a long, stateful, all-or-nothing job run inside one execution — and the day it fails at minute nine, you have no idea what already happened, no checkpoint, and no way to resume. You removed the alarm, not the fire.
Name the shapes and match the tool to each. If the video processing is genuinely one long computation that cannot be broken up, then long-running compute is the honest need — but a function is a poor host for it, and a container job (Class Twenty-Seven) or a Durable Functions activity with the right plan is a better one. If, more likely, it is several steps — download, transcode, thumbnail, store — then it wants a Durable Functions orchestration: each step its own short activity, checkpointed, resumable, fanning out across sizes if needed. Either way the question to ask first is not "how do I make one function run longer" but "why is this one function doing so much."
Close on the cost the quick fix hides. An App Service plan does not scale to zero, so the moment they move there to dodge the timeout, they also give up the per-run billing that made Functions worth choosing — the video job now bills for a standing plan whether or not a video is being processed. That may be fine; it should be a decision, not a side effect. The sentence to leave them with: the limit isn't the problem — it's the diagnosis; fix the shape of the work, and pick the plan on purpose.
Resist the reflex to re-architect before you have read the invoice. "Move it back to a VM" is a large, slow change proposed before anyone has looked at why the number moved — the exact instinct Class Thirty-Two trains you to distrust. Consumption bills per execution and per gigabyte-second, so a bill that jumped almost always means executions or duration jumped. The first move is the metrics, not the migration: how many invocations, of which function, and did the count or the per-run duration change?
Name the usual suspects, because they are specific and fixable. The classic is a runaway or recursive trigger — a function whose output lands on the queue that triggers it, or a retry storm on a persistently failing message multiplying invocations — turning a bug into a per-run invoice. Next is a chatty trigger firing far more than intended (a timer set to every second, a blob trigger on a hot container). Then a genuine, healthy traffic increase, in which case the bill rose because the business did, and the VM would simply have hit a wall instead of a line item. Each has a targeted fix — cap concurrency, fix the loop, dead-letter the poison, right-size the schedule — none of which is "buy a server."
Only then compare honestly, at the real workload. If, after fixing the anomaly, sustained high-volume traffic genuinely makes Consumption more expensive than a right-sized always-on plan, that is a legitimate finding — Consumption wins on spiky and bursty, and a steady firehose can favour a plan with a fixed floor. But that is a priced comparison at the true run rate, reviewed at the Class Thirty-Two monthly, not a panic migration off one surprising invoice. The sentence that keeps it honest: per-run billing didn't overcharge you — it itemised something, and the first job is to read what.
Five things worth carrying out of this class
- A Function is code with a trigger — it does not run until an event wakes it, runs for the work, and disappears. Serverless means the servers are Microsoft's problem; the function is yours. You bring the function; Azure brings everything around it.
- The model is one trigger, the code, and zero-or-more bindings. The trigger wakes it (queue, Service Bus, timer, HTTP, blob); bindings carry data in and out declaratively, so the code is about the work, not the plumbing.
- The hosting plan is the decision that bites: Consumption (scale to zero, cheap, cold starts, ~10-min cap), Premium/Flex (pre-warmed, VNet, long runs), App Service plan (always on). Start on Consumption; move up only when a named requirement forces you.
- Functions are stateless, must be idempotent (at-least-once triggers), and should be short and single-purpose. State in memory is a bug that waits for the second instance.
- Multi-step workflows that wait or fan out want Durable Functions — an orchestrator that checkpoints state and survives restarts. Name it when the interviewer describes a process, not a single message.
- The Functions hosting-plan lineup is mid-evolution: Flex Consumption keeps scale-to-zero and per-execution billing while shrinking cold starts (with paid always-ready instances to remove them outright), and Microsoft's guidance now recommends it for new serverless apps while the classic Linux Consumption plan walks a retirement path. Names and exact limits shift release to release; treat the plan trade-offs in §3 as stable — scale-to-zero versus cold starts versus always-on — and check the current plan-comparison page for the specific numbers before you design. ↩
- On classic Consumption the timeout default is five minutes, configurable up to a ten-minute hard ceiling — the default is the floor, not the cap. Flex Consumption and Premium default to thirty minutes and can be raised much further; Dedicated is effectively unbounded. Do not memorise a single number — memorise that serverless plans cap runs in minutes and that a job which needs longer is telling you something about its shape, which §4 and §5 are the answer to. ↩
- This class teaches Azure Functions because it is Azure's serverless compute and what AZ-204 expects, but the model outranks the product: an event triggers a short, stateless, idempotent unit of work, and long workflows move to a checkpointed orchestrator. Meet AWS Lambda with Step Functions, Google Cloud Functions with Workflows, or Cloudflare Workers and you will find the same shape — learn triggers, bindings, statelessness, and orchestration here once, and the products become configuration. ↩