---
title: 'Pricing Models'
source: 'https://academia.sh/en/courses/cloud-fundamentals/pricing-models'
course: 'Cloud Fundamentals'
language: en
updated: '2026-09-13T05:07:33+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Runs three capacity models with fictional prices against the same load profile and compares total cost'
    - "Counts spot capacity's two hidden items: lost work and the cost of re-running it"
    - 'Measures unused commitment in reserved capacity and separates list price from effective unit price'
    - 'Shows with numbers across three profiles that the winning model changes as the load profile changes'
---

# Pricing Models

Running on-demand, reserved, and spot capacity against the same load profile: total cost, work re-run because of reclaimed capacity, unused commitment, the gap between list price and effective unit price, and the liability left when exiting mid-window.

The previous lesson counted cost as a pillar and found that none of the twelve decisions
raised it. The reason was not that the decisions were poor, but that all of them were
about **what** is rented. This lesson's question is different: the same capacity, the
same hour, the same work — **under which terms** is it rented.

There are three terms. **On-demand capacity** is taken the moment it is requested and
stops being paid for the moment it is released; no decision is handed over, and the unit
price is highest. **Reserved capacity** gives a discount in exchange for a commitment to
pay for a fixed amount across the window; the decision handed over is the decision to
**shrink capacity**. **Spot capacity** gives the largest discount, but the provider can
reclaim it; the decision handed over is the decision of **when the work gets cut**. The
measurement below runs these three terms under the same load.

## Three Models and Load Profiles

- **CG19.** Prices are **fictional** and in fictional `unit/cu-hour`: on-demand 1.00,
  reserved 0.62, spot 0.28. No real currency or price list is used.
- **CG20.** The window is 168 hours. The unpredictable profile is generated with a
  self-written generator; the seed is visible as 20260804 and every run gives the same
  sequence.
- **CG21.** The reserved commitment R is the average demand, rounded, and is paid across
  the window whether it is used or not; demand exceeding R is served on-demand.
- **CG22.** Spot capacity's reclaim probability depends on load:
  $p = 0.02 + 0.40 \cdot (demand / 120)$, at most 0.45.
- **CG23.** Only **deferrable** work runs on spot capacity. The deferrable share is
  0.45 / 0.60 / 0.10 depending on the profile, because the three profiles are produced by
  different kinds of work.
- **CG24.** The work reclaimed capacity was doing is lost and re-runs on on-demand
  capacity; the re-run multiplier is 2.5 (the loss of intermediate state and re-queuing).

```js
// price/model.mjs — pricing is a MODEL. Prices are FICTIONAL and in fictional
// `unit/cu-hour`; no real currency, provider, or price list is used.
export function generator(seed) {                  // self-written generator, seed visible
  let s = seed >>> 0;
  return () => ((s = (Math.imul(s, 1664525) + 1013904223) >>> 0) / 4294967296);
}
export const SEED = 20260804, HOURS = 168;          // one-week window

export const PRICE = { onDemand: 1.00, reserved: 0.62, spot: 0.28 };  // fictional unit/cu-hour
export const RERUN = 2.5;                           // re-run multiplier for lost work
export const MARKET_PEAK = 120;                     // fictional: scale point of reclaim probability
export const reclaim = (d) => Math.min(0.45, 0.02 + 0.40 * (d / MARKET_PEAK));

// profile: demand series + deferrable share (differs because the profile's source work differs)
export function profiles() {
  const r = generator(SEED);
  const flat = Array.from({ length: HOURS }, () => 40);
  const spiky = Array.from({ length: HOURS }, (_, t) => (t % 24 >= 2 && t % 24 <= 4 ? 120 : 20));
  const unpredictable = Array.from({ length: HOURS }, () =>
    (r() < 0.6 ? Math.round(r() * 6) : 55 + Math.round(r() * 85)));
  return { flat: [flat, 0.45], spiky: [spiky, 0.60], unpredictable: [unpredictable, 0.10] };
}

export function run(model, demand, deferrable) {
  const R = Math.round(demand.reduce((a, b) => a + b, 0) / demand.length);  // commitment = avg. demand
  let cost = 0, loss = 0, unusedCommitment = 0, rerunCost = 0;
  for (const d of demand) {
    if (model === "on-demand") cost += d * PRICE.onDemand;
    else if (model === "reserved") {
      cost += R * PRICE.reserved + Math.max(0, d - R) * PRICE.onDemand;
      unusedCommitment += Math.max(0, R - d);
    } else {
      const q = d * deferrable, p = reclaim(d), k = q * p;
      cost += q * (1 - p) * PRICE.spot + k * RERUN * PRICE.onDemand
        + (d - q) * PRICE.onDemand;
      loss += k; rerunCost += k * RERUN * PRICE.onDemand;
    }
  }
  return { model, cost, loss, unusedCommitment, rerunCost, R };
}
export const MODEL = ["on-demand", "reserved", "spot"];
```

## Three Runs Against the Same Profile

```js
// price/run.mjs — three models run against the same load profile; total cost, work lost
// on spot, and unused commitment on reserved are counted together
import { profiles, run, MODEL, HOURS, SEED, PRICE } from "./model.mjs";

const s = (x, n) => String(x).padStart(n);
const y = (x, n = 2) => x.toFixed(n);
const P = profiles();

console.log(`seed ${SEED}, window ${HOURS} hours; prices are FICTIONAL unit/cu-hour: ` +
  `on-demand ${y(PRICE.onDemand)}, reserved ${y(PRICE.reserved)}, spot ${y(PRICE.spot)}.`);
console.log();
console.log("profile         | total demand   | avg    | lowest   | highest   | deferrable share   | commitment R  ");
console.log("----------------|----------------|--------|----------|-----------|--------------------|---------------");
for (const [name, [t, e]] of Object.entries(P)) {
  const top = t.reduce((a, b) => a + b, 0);
  console.log(`${name.padEnd(15)} | ${s(top, 14)} | ${s(y(top / HOURS, 1), 6)} | ${s(Math.min(...t), 8)} | ` +
    `${s(Math.max(...t), 9)} | ${s(y(e), 18)} | ${s(run("reserved", t, e).R, 14)}`);
}

console.log();
console.log("profile         | model        | total cost     | lost work     | rerun cost    | unused commitment  ");
console.log("----------------|--------------|----------------|---------------|---------------|--------------------");
const winner = {};
for (const [name, [t, e]] of Object.entries(P)) {
  let best = null;
  for (const m of MODEL) {
    const r = run(m, t, e);
    if (best === null || r.cost < best.cost) best = r;
    console.log(`${name.padEnd(15)} | ${m.padEnd(12)} | ${s(y(r.cost), 14)} | ` +
      `${s(r.loss ? y(r.loss, 1) : "-", 13)} | ${s(r.rerunCost ? y(r.rerunCost) : "-", 13)} | ` +
      `${s(r.unusedCommitment ? y(r.unusedCommitment, 1) : "-", 18)}`);
  }
  winner[name] = best;
}

console.log();
console.log("profile         | winner       | gap to 2nd    | gap to 2nd %   | gap to priciest %  ");
console.log("----------------|--------------|---------------|----------------|--------------------");
for (const [name, [t, e]] of Object.entries(P)) {
  const ranked = MODEL.map((m) => run(m, t, e)).sort((a, b) => a.cost - b.cost);
  const [a, b, c] = ranked;
  console.log(`${name.padEnd(15)} | ${a.model.padEnd(12)} | ${s(y(b.cost - a.cost), 13)} | ` +
    `${s(`${y((100 * (b.cost - a.cost)) / a.cost, 1)}%`, 14)} | ` +
    `${s(`${y((100 * (c.cost - a.cost)) / a.cost, 1)}%`, 18)}`);
}
console.log(`winning model across the three profiles: ${Object.values(winner).map((k) => k.model).join(", ")}.`);
```

```
seed 20260804, window 168 hours; prices are FICTIONAL unit/cu-hour: on-demand 1.00, reserved 0.62, spot 0.28.

profile         | total demand   | avg    | lowest   | highest   | deferrable share   | commitment R  
----------------|----------------|--------|----------|-----------|--------------------|---------------
flat            |           6720 |   40.0 |       40 |        40 |               0.45 |             40
spiky           |           5460 |   32.5 |       20 |       120 |               0.60 |             33
unpredictable   |           7430 |   44.2 |        0 |       138 |               0.10 |             44

profile         | model        | total cost     | lost work     | rerun cost    | unused commitment  
----------------|--------------|----------------|---------------|---------------|--------------------
flat            | on-demand    |        6720.00 |             - |             - |                  -
flat            | reserved     |        4166.40 |             - |             - |                  -
flat            | spot         |        5572.09 |         463.7 |       1159.20 |                  -
spiky           | on-demand    |        5460.00 |             - |             - |                  -
spiky           | reserved     |        5264.28 |             - |             - |             1911.0
spiky           | spot         |        4850.46 |         787.9 |       1969.80 |                  -
unpredictable   | on-demand    |        7430.00 |             - |             - |                  -
unpredictable   | reserved     |        8528.04 |             - |             - |             3907.0
unpredictable   | spot         |        7475.12 |         261.3 |        653.25 |                  -

profile         | winner       | gap to 2nd    | gap to 2nd %   | gap to priciest %  
----------------|--------------|---------------|----------------|--------------------
flat            | reserved     |       1405.69 |          33.7% |              61.3%
spiky           | spot         |        413.82 |           8.5% |              12.6%
unpredictable   | on-demand    |         45.12 |           0.6% |              14.8%
winning model across the three profiles: reserved, spot, on-demand.
```

The last line is this lesson's finding: **three different models win across the three
profiles.** In the flat profile, reserved capacity wins at 4,166.40 units, while the same
load costs 6,720.00 on demand — 61.3 percent more; because the commitment is used in full,
unused commitment is zero. In
the spiky profile, reserved capacity loses, because the commitment is set to the average
and 87.5 percent of demand falls below that average: **1,911 cu-hours** are paid for but
unused. In the unpredictable profile, no handoff pays off at all, and on-demand capacity,
at the highest list price, wins at 7,430.00 units.

Spot capacity's two hidden items sit in separate columns in the middle table. In the flat
profile, 463.7 cu-hours of work is lost along with the reclaimed capacity, and re-running
it costs **1,159.20 units** — more than a fifth of that model's total cost. In the spiky
profile, the loss is 787.9 cu-hours and the re-run cost is 1,969.80 units; even though
this profile is the winner, 40 percent of its cost is the price of reclaimed work. **Spot
capacity's real price is not in the list; it is in the reclaim rate.**

The spot row in the unpredictable profile shows this item's second face: the lost work,
at 261.3 cu-hours, is the lowest of the three profiles, yet the model still loses
(7,475.12 against 7,430.00). The reason is not the loss but the ceiling on the gain — in
that profile the deferrable share is 0.10, so the discount can only reach a tenth of
demand. **A discount is a discount only up to the share it can touch.**

The third table gives the size of the gaps. In the flat profile, the winner beats the
runner-up by 33.7 percent: model choice is a real decision here. In the unpredictable
profile, the gap falls to 0.6 percent — the cost of the three models is nearly identical,
and the choice is decided not by cost but by **risk**.

## Effective Unit Price and the Cost of Exit

List price does not say how much a lease actually costs. What needs to be measured is the
**effective unit price**: total cost divided by the total demand served.

```js
// price/exit.mjs — effective unit price, the decision handed over, and the cost of exiting mid-window
import { profiles, run, MODEL, HOURS, PRICE } from "./model.mjs";

const s = (x, n) => String(x).padStart(n);
const y = (x, n = 3) => x.toFixed(n);
const P = profiles(), EXIT_HOUR = 84;               // the lease is exited mid-window
const HANDOFF = { "on-demand": 0, reserved: 1, spot: 2 };
const LIST = { "on-demand": PRICE.onDemand, reserved: PRICE.reserved, spot: PRICE.spot };

console.log("profile         | model        | list   | effective unit   | vs. list     | handoff          ");
console.log("----------------|--------------|--------|------------------|--------------|------------------");
for (const [name, [t, e]] of Object.entries(P)) {
  const top = t.reduce((a, b) => a + b, 0);
  for (const m of MODEL) {
    const effective = run(m, t, e).cost / top;
    console.log(`${name.padEnd(15)} | ${m.padEnd(12)} | ${s(y(LIST[m], 2), 6)} | ${s(y(effective), 16)} | ` +
      `${s(`x${y(effective / LIST[m], 2)}`, 12)} | ${s(HANDOFF[m], 17)}`);
  }
}

console.log();
console.log(`remaining liability when exiting in hour ${EXIT_HOUR} of the window (fictional units):`);
console.log("profile         | on-demand    | spot      | reserved (remaining commitment)   | share of reserved gain   ");
console.log("----------------|--------------|-----------|-----------------------------------|--------------------------");
for (const [name, [t, e]] of Object.entries(P)) {
  const r = run("reserved", t, e), full = run("on-demand", t, e);
  const remaining = (HOURS - EXIT_HOUR) * r.R * PRICE.reserved;
  const gain = full.cost - r.cost;
  console.log(`${name.padEnd(15)} | ${s(0, 12)} | ${s(0, 9)} | ${s(y(remaining, 2), 33)} | ` +
    `${s(gain > 0 ? `${y((100 * remaining) / gain, 1)}%` : "no gain", 25)}`);
}
```

```
profile         | model        | list   | effective unit   | vs. list     | handoff          
----------------|--------------|--------|------------------|--------------|------------------
flat            | on-demand    |   1.00 |            1.000 |        x1.00 |                 0
flat            | reserved     |   0.62 |            0.620 |        x1.00 |                 1
flat            | spot         |   0.28 |            0.829 |        x2.96 |                 2
spiky           | on-demand    |   1.00 |            1.000 |        x1.00 |                 0
spiky           | reserved     |   0.62 |            0.964 |        x1.56 |                 1
spiky           | spot         |   0.28 |            0.888 |        x3.17 |                 2
unpredictable   | on-demand    |   1.00 |            1.000 |        x1.00 |                 0
unpredictable   | reserved     |   0.62 |            1.148 |        x1.85 |                 1
unpredictable   | spot         |   0.28 |            1.006 |        x3.59 |                 2

remaining liability when exiting in hour 84 of the window (fictional units):
profile         | on-demand    | spot      | reserved (remaining commitment)   | share of reserved gain   
----------------|--------------|-----------|-----------------------------------|--------------------------
flat            |            0 |         0 |                           2083.20 |                     81.6%
spiky           |            0 |         0 |                           1718.64 |                    878.1%
unpredictable   |            0 |         0 |                           2291.52 |                   no gain
```

Spot capacity's list price is 0.28, 72 percent below on-demand. Its effective unit
price, though, is 0.829 in the flat profile, 0.888 in the spiky profile, and **1.006** in
the unpredictable profile — 2.96, 3.17, and 3.59 times the list price, respectively. In
the unpredictable profile, the model that looks cheapest actually ends up **more
expensive** than the one that looks priciest. The same pattern plays out more mildly for
reserved capacity: at a list of 0.62, the effective price is 0.964 in the spiky profile
and 1.148 in the unpredictable one.

The handoff column ranks these three: 0 for on-demand, 1 for reserved, 2 for spot. The
order matches the list discount exactly — the discount is the payoff for the decision
handed over, and all three models' effective price depends on how many times that
handed-over decision worked against them.

The last table counts the exit. For on-demand and spot capacity, the remaining liability
from exiting mid-window is **zero**. For reserved capacity, in the flat profile, 2,083.20
units remain — that is **81.6 percent** of the total gain that same model produced in
that profile. In the spiky profile, the remaining liability is **878.1 percent** of the
gain: exiting in the middle of a commitment wipes out the commitment's entire gain eight
times over. The exit cost of a lease, here, is not an estimate; it is a number the
contract writes down directly.

## Summary

- The three capacity models rank by the number of decisions handed over: on-demand 0,
  reserved 1 (shrinking capacity), spot 2 (when the work gets cut); the list discount
  follows this order.
- When the same three models run against three load profiles, **three different models
  win**: reserved in the flat profile, spot in the spiky profile, on-demand in the
  unpredictable profile. The runner-up costs 33.7, 8.5 and 0.6 percent more than the
  winner, respectively. There is no single correct model.
- Spot capacity's hidden item is reclaimed work: 463.7 cu-hours lost and 1,159.20 units of
  re-run cost in the flat profile; 787.9 cu-hours and 1,969.80 units in the spiky profile.
- Reserved capacity's hidden item is unused commitment: 1,911 cu-hours are paid for and
  unused in the spiky profile, 3,907 in the unpredictable profile.
- List price and effective unit price are separate numbers: spot's list is 0.28, but its
  effective price ranges from 0.829 to 1.006 (2.96–3.59 times the list) and, in the
  unpredictable profile, exceeds on-demand.
- At exit, the remaining liability of on-demand and spot capacity is zero; for reserved
  capacity, exiting mid-window wipes out 81.6 percent of the gain in the flat profile and
  878.1 percent in the spiky profile.

## Next Step

Four lessons measured the geography and the price of rented infrastructure:
failure-domain separation, proximity to the user, tension between pillars, and the terms
under which capacity is taken. All four assumed one thing — that the work to be rented is
**already rented**. But the fictional regional measurement network runs today in its own
room, and it has to get here from somewhere. The same service can be moved as it stands,
its individual pieces can be swapped for rented counterparts, or it can be rewritten from
scratch; the three do not give the same result. The next lesson compares these three
migration paths in terms of the **decision handed over**: how many decisions pass to the
provider on each path, how many items get rewritten, how many waves the migration takes
to complete, and, most importantly, how much the exit cost counted across these four
lessons grows on each path.
