Cost Optimization and Technology Financial Management Questions
Analyzing and reducing technology and enterprise costs and applying financial discipline to technology spend. Covers total cost of ownership modelling (cost lines, discounting and NPV, sensitivity analysis, depreciation, sunk and stranded costs, decommissioning), fixed and variable cost structure and unit cost, spend analysis and cost leakage, make-versus-buy and capex-versus-opex decisions, and the ROI and payback case for technology investments. Includes cost allocation, showback and chargeback of shared platforms, software licensing and contract economics (perpetual, subscription, per-seat, consumption, renewals, escalators and commitments), and the supplier terms that change what a purchase really costs. Emphasizes designing and governing cost reduction programs, prioritizing initiatives, verifying that savings are real and not double-counted, protecting quality and reliability while cutting, and surfacing hidden or downstream costs. Cloud-bill engineering, sourcing and supplier negotiation, and budgeting-process mechanics are covered elsewhere.
A sourcing program claims $4M of savings. How would you verify that, and what would you want in place so future claims can be trusted?
Sample Answer
Direct answer
Do not accept a savings figure; rebuild it. Get the claim's formula, tie each input to the ledger (the accounting record of what was actually paid) and invoices, separate price effects from volume and market effects, net off implementation costs and offsets, and classify what is recurring versus one-time. Then put controls in place so every future claim uses the same definitions and is signed off by someone independent of the person claiming it.
Step-by-step verification
- Get the claim's formula and baseline. Typically: (old price minus new price) x baseline volume. Which old price, which volume, which period?
- Tie to actuals. Compare invoices and purchase orders (POs, the signed requests to buy) after the change to the old prices; confirm the new price is actually being paid and on which items.
- Adjust for volume. If volume changed, savings are price change x actual volume, not baseline volume. Volume falling for demand reasons is not a saving.
- Check the baseline. Was the old price a real alternative (market price had moved?) or a stale list price? Savings against a market-adjusted baseline are smaller than against history, and the part you would have got anyway is not program savings and is reported separately: a market effect if prices were falling and you simply rode the decline, or cost avoidance (spend that would have risen but did not, as opposed to spend that actually fell) if prices were rising and the contract held yours flat.
- Net gross against costs. Subtract one-time implementation costs (migration, switching, licence break fees meaning penalties for leaving an old contract early) and recurring offsets (higher freight, support, quality failures, reduced rebates elsewhere, where a rebate is a volume-based refund from a supplier).
- Classify and date. Recurring run-rate versus one-time; realised in the period versus annualised.
- Check double counting. The same saving may be claimed by sourcing, engineering and finance.
Worked example (illustrative figures)
Claim: $4M, built as baseline volume 500,000 units x ($50 old price minus $42 new price) = $4,000,000 per year.
| View | Calculation | Amount |
|---|---|---|
| Claim as stated | 500,000 x ($50 - $42) | $4.00M |
| Re-based on actual volume (380,000 units) | 380,000 x $8 | $3.04M |
| Volume shortfall that was never a saving | $4.00M - $3.04M | $0.96M |
| Net of $0.6M one-time implementation cost | $3.04M - $0.6M | $2.44M (first year) |
| Against a market baseline of $46 | 380,000 x ($46 - $42) | $1.52M |
The last two rows are different questions answered on different bases, and they are also on different cost bases: the $2.44M is net of the $0.6M one-time cost while the $1.52M is not. On the same net-of-cost basis the market-baseline figure is $1.52M - $0.6M = $0.92M in year one, so compare $2.44M with $0.92M, or $3.04M with $1.52M, never $2.44M with $1.52M. As to the baselines themselves: the first measures against what we used to pay, the other against what the market would have charged anyway. The program must define which baseline counts and state it in advance. If the answer is the historical price, the verified recurring figure is $3.04M, with $2.44M in year one after the one-time cost. If no baseline has been agreed yet, quote $3.04M (and $2.44M for year one) to leadership as the verified figure against what we used to pay, and show $1.52M beside it, labelled as the saving against the market price, so nobody reads the two as the same thing.
Making future claims trustworthy
- Written savings definitions: baseline rules, treatment of volume and market movement, cost avoidance vs savings vs one-time, net or gross.
- Named KPI owners and data owners. The person who claims a saving does not validate it; finance or an analytics team does.
- Single source of truth: a savings register (one list of every claimed saving, its owner and its evidence) tied to ledger lines, with the evidence (contract, invoice, PO) linked.
- Audit cadence: independent sampling of claims on a schedule (for example quarterly) plus a re-check twelve months later, because prices creep back.
- Dispute path: claimant, then program office, then a named finance arbiter, with time limits.
- Incentives: pay rebates, bonuses and supplier incentive payments only on verified savings, with clawback (taking the payment back) if the saving does not persist.
Pitfalls
Accepting a headline figure because the contract looks good; verifying only gross price; verifying once and never again.
A client wants to break ten monolithic applications into containerised microservices. How would you build the multi-year TCO for that program, and how would you judge whether the benefits justify the added operational complexity?
Sample Answer
Direct answer
Build the TCO (total cost of ownership) as a five-year, year-by-year model with two columns for every cost line: stay as-is, and split into services. The cost of the program is the one-off migration plus a permanent increase in the cost of running distributed systems (a platform team, tooling, on-call), and the benefits are only the ones you can measure. Then judge the complexity as a cost with a number on it, per application, rather than as a verdict on microservices. On the illustrative model below, splitting all ten applications does not clear its cost, and splitting only the ones that change often is the better scope, though still short of break-even (the benefit at which the NPV is exactly zero) on the assumed benefit; the deciding evidence would be a measured delay in shipping features that the split removes.
Cost lines to include
| Group | Lines |
|---|---|
| One-off | Refactoring and carving up the data (splitting one shared database into one per service), rebuilding deployment pipelines, migration and cutover, dual running of old and new (paying for both while they overlap), retraining |
| Recurring, new | Platform team for the cluster, CI/CD (continuous integration and delivery) and observability (logs, metrics and traces that show what each service is doing), tooling licences and extra monitoring data, more on-call and incident load from failures that now cross services, network traffic between services, extra test environments |
| Recurring, changed | Cloud runtime (right-sized, meaning each service gets only the capacity it needs, but with idle overhead per service) |
| Benefits | Delivery time freed from release coordination (lead time, the days from commit to production, and deployment frequency), independent scaling that lets you size one hot service and not the whole monolith, smaller blast radius for failures (fewer things break when one part fails), faster hiring and onboarding by team ownership, revenue from earlier launches (speed to market) |
Include the alternative that is usually missing: a modular monolith (one deployable application with strict internal module boundaries) with better pipelines, which captures some of the delivery benefit at a fraction of the operating cost. As an illustration, suppose it costs 4 engineer-months per high-change application ($60k each, $180k for three, all in year 0) with no platform team or extra tooling, and captures one third of the benefit ($103k a year per application, from year 2). The PV benefit is $103.3k x 3 x 3.0668 = $951k against a $180k cost, an NPV of about +$771k. Those inputs are assumptions to test, not findings, but they show why the comparison belongs in the model.
Worked example (illustrative inputs)
One engineer-month costs $15k fully loaded. Per application: 18 engineer-months of migration split over years 0 and 1, $25k a year of tooling and monitoring data from year 1, $30k of dual running in year 1, and 0.25 of an engineer of extra on-call. A shared platform team of 3 engineers from year 0 (half a year in year 0). Benefit per benefiting application: 1.5 engineers of freed delivery time plus $40k a year of right-sizing, starting in year 2. Discount rate 8%. The benefit per benefiting application is 1.5 engineers x 12 months x $15k = $270k of freed delivery time plus $40k of right-sizing, which is $310k a year. PV (present value) below means each year's amount divided by 1.08 once per year, so it is in today's money.
# Illustrative 5-year model in $k. One engineer-month costs 15 (about 180 a year, fully loaded).
RATE = 0.08
ENG_MONTH = 15
BENEFIT_YEARS = range(2, 6) # benefits start in year 2, after the first services are live
def pv(x, year):
return x / (1 + RATE) ** year
def yearly_costs(n_apps):
costs = {y: 0.0 for y in range(0, 6)}
migration = n_apps * 18 * ENG_MONTH # 18 engineer-months per app, split over years 0 and 1
costs[0] += migration / 2
costs[1] += migration / 2
for y in range(0, 6): # platform team: 3 engineers (half a year in year 0)
costs[y] += 3 * (6 if y == 0 else 12) * ENG_MONTH
for y in range(1, 6):
costs[y] += 25 * n_apps # tooling licences and observability data
costs[y] += 0.25 * 12 * ENG_MONTH * n_apps # extra on-call and incident load
costs[1] += 30 * n_apps # dual running of old and new versions, year 1 only
return costs
ASSUMED_BENEFIT = 1.5 * 12 * ENG_MONTH + 40 # per benefiting app per year: freed engineer time + right-sizing
for label, n_apps, benefiting in (("all 10 apps, 3 benefit", 10, 3),
("3 high-change apps only", 3, 3),
("all 10 apps, 6 benefit", 10, 6)):
pv_cost = sum(pv(v, y) for y, v in yearly_costs(n_apps).items())
ben_factor = sum(pv(1, y) for y in BENEFIT_YEARS)
pv_benefit = ASSUMED_BENEFIT * benefiting * ben_factor
breakeven = pv_cost / ben_factor / benefiting
print(f"{label}: PV cost {pv_cost:,.0f} PV benefit {pv_benefit:,.0f} NPV {pv_benefit - pv_cost:,.0f} "
f"break-even benefit per benefiting app per year {breakeven:,.0f}")
print("all 10 apps, cost by year:", {y: round(v) for y, v in yearly_costs(10).items()})
print("assumed benefit per benefiting app per year:", ASSUMED_BENEFIT)
all 10 apps, 3 benefit: PV cost 8,099 PV benefit 2,852 NPV -5,247 break-even benefit per benefiting app per year 880
3 high-change apps only: PV cost 4,128 PV benefit 2,852 NPV -1,276 break-even benefit per benefiting app per year 449
all 10 apps, 6 benefit: PV cost 8,099 PV benefit 5,704 NPV -2,395 break-even benefit per benefiting app per year 440
all 10 apps, cost by year: {0: 1620, 1: 2890, 2: 1240, 3: 1240, 4: 1240, 5: 1240}
assumed benefit per benefiting app per year: 310.0
Reading it, all in $k of present value:
- Splitting all ten applications costs $8,099k, and if only 3 of the 10 benefit, the benefit is $2,852k, an NPV (net present value, discounted benefits minus discounted costs) of -$5,247k. The benefit lasts four years (years 2 to 5), and $1 a year over those years is worth 0.8573 + 0.7938 + 0.7350 + 0.6806 = 3.0668 in today's money. So the PV benefit is $310k x 3 apps x 3.0668 = $2,852k, and the break-even is $8,099k / 3.0668 / 3 = $880k a year per benefiting application, against the $310k assumed.
- Splitting only the three that change fastest costs $4,128k for the same $2,852k benefit, an NPV of -$1,276k, with a break-even of $449k per application per year.
- Even if 6 of the 10 benefit, the NPV is -$2,395k, because the cost is driven by the number of applications split and the benefit by those that change often.
So on these inputs a pure efficiency case does not clear the bar. A go decision would need evidence the model does not contain: a measured backlog of revenue-bearing features stuck behind the monolith's release cycle, or a platform that already exists and removes the shared team cost.
How I would judge benefit against complexity
- Measure today: lead time, deployment frequency (how often you release) and change failure rate (the share of releases that cause an incident or rollback) for each application, and the share of releases blocked by cross-team coordination.
- Rank the ten by change rate and by independent scaling need. Split an application only if both a team boundary and a change-rate or scaling reason exist.
- Fund a pilot of one or two services with a pre-agreed gate: the measured lead time and incident rate must move by the amount assumed in the model, or the program stops.
- Re-run the model after the pilot with actual migration cost per application, because the 18 engineer-months is the input most likely to be wrong.
Trade-offs and pitfalls
- Counting benefits as "scalability" with no number is the usual way these cases pass; each benefit needs an owner and a measure.
- The platform team is a fixed cost, so the per-application cost falls as more applications share it. That pushes toward either a real program or none, which is why scope is the main lever.
- Horizon matters: with benefits starting in year 2 and no value counted after year 5, the model is harsh on any program whose payoff arrives late. Show a ten-year view alongside, but do not extend the horizon just to get a pass.
- What would change my call: a platform team that already exists, a regulatory or availability need for isolation, or a measured delivery bottleneck worth more than $449k per application per year.
How would you attribute the full cost of running a multi-tenant SaaS product to individual features, including shared infrastructure, support and the upkeep of old code, and what decisions would that attribution inform?
Sample Answer
Direct answer
I would build the attribution in layers: charge each feature the infrastructure it provably uses (direct tags), allocate shared infrastructure and shared platform services by a measured usage driver (CPU-seconds, meaning seconds of processor time a feature consumes, or API calls), assign support cost by tagged ticket volume, and assign upkeep of old code by engineering hours spent on each feature. The result is a fully loaded cost per feature (every cost layer added up, not just the cloud bill), then a cost per active account, which feeds pricing and packaging (deciding which plan tier or add-on a feature belongs in and what to charge for it), sunset (retiring a feature), and investment decisions. Allocation is a decision aid, so every driver must be explainable and the model must be stable enough to compare month to month.
The layers and their drivers
| Cost layer | What it is | Allocation driver | Where the data comes from |
|---|---|---|---|
| Direct infrastructure | Resources used by one feature | Resource tags or service ownership | Cloud billing export |
| Shared infrastructure | Databases, clusters, queues many features use | Share of CPU-seconds (or request count) per feature | Metering in the application or the platform |
| Shared platform | Auth, API gateway, the platform team | Share of API calls | Gateway logs |
| Support | Support team cost | Tickets tagged by feature x average handling time | Ticketing tool with a "feature" field |
| Upkeep of old code | Bug fixes, dependency upgrades, on-call for legacy | Engineering hours by feature | Issue tracker labels, pull request paths |
Rules that keep it honest: allocate 100% of cost (an unallocated pool hides the expensive things), pick the driver that best reflects why the cost exists (a database is used by queries, not by features that happen to exist), and publish the driver shares so product owners can challenge them. Multi-tenant products (one deployment serving many customer organisations, the tenants) need a second cut by tenant tier (for example Standard versus Enterprise plans), because a few heavy tenants can drive a feature's shared cost; attribute usage to tenant and feature, then roll up either way. Illustration using Reports' $72.0k shared infrastructure from the table below: suppose 80 Enterprise accounts (2% of its 4,000) consume 30% of its CPU-seconds. They carry $21.6k, or $270 per account per month, while the other 3,920 accounts carry $50.4k, or $12.86 each. The feature-level average of $18.00 ($72.0k / 4,000) hides that difference.
Worked example (illustrative figures, monthly)
Total monthly run cost is $800k: direct infrastructure $150k, shared infrastructure $240k, shared platform $90k, support $120k, upkeep $200k.
| Feature | Direct | Shared infra | Platform | Support | Upkeep | Total | Active accounts | Cost per active account |
|---|---|---|---|---|---|---|---|---|
| Reports | 40.0 | 72.0 | 22.5 | 24.0 | 40.0 | $198.5k | 4,000 | $49.63 |
| Search | 55.0 | 60.0 | 31.5 | 12.0 | 30.0 | $188.5k | 6,000 | $31.42 |
| Integrations | 20.0 | 36.0 | 22.5 | 48.0 | 40.0 | $166.5k | 1,500 | $111.00 |
| Dashboards | 25.0 | 48.0 | 9.0 | 12.0 | 20.0 | $114.0k | 5,000 | $22.80 |
| Legacy export | 10.0 | 24.0 | 4.5 | 24.0 | 70.0 | $132.5k | 150 | $883.33 |
(All cost columns in $k per month. Shared infra drivers: CPU-second shares 30%, 25%, 15%, 20%, 10% of $240k. Platform: API call shares 25%, 35%, 25%, 10%, 5% of $90k. Support: ticket shares 20%, 10%, 40%, 10%, 20% of $120k. Upkeep: hour shares 20%, 15%, 20%, 10%, 35% of $200k.) Check: 198.5 + 188.5 + 166.5 + 114.0 + 132.5 = $800.0k.
Decisions the numbers inform
- Sunset or reprice: Legacy export costs $132.5k a month for 150 active accounts, $883 per account, driven by upkeep ($70k). Options: migrate those accounts to Reports, charge for it, or retire it with a deadline. Its direct infrastructure is only $10k, so a view that counted only cloud tags would have called it cheap. Before deciding, split the $132.5k into avoidable cost (what actually disappears if the feature is retired) and cost that stays. Illustration: the $10.0k direct infrastructure goes; the $70.0k upkeep goes if those engineers move to other work; and half of the $24.0k support, $12.0k, goes. The $24.0k shared infrastructure and $4.5k platform share stay, because the databases and gateway still serve the other features, and the other $12.0k of support stays. Avoidable: 10.0 + 70.0 + 12.0 = $92.0k a month. Staying, to be re-spread over the remaining features: 24.0 + 4.5 + 12.0 = $40.5k. Together $132.5k.
- Packaging: Integrations costs $111 per active account, mostly support and upkeep. Price it as an add-on or invest in self-serve docs and fewer breaking changes.
- Investment: Reports is the largest pool ($198.5k) and Search has the widest reach (6,000 accounts at $31.42 each). A 10% efficiency gain is worth $19.85k a month on Reports and $18.85k on Search, so Reports is the bigger single lever in dollars, while Search's gain lowers the cost of serving the most customers. Compare that with the engineering effort each would need.
- Margin by tier: join cost per account with revenue per account to see which plan tier is underwater (costs more to serve than it brings in). Gross margin is revenue minus the cost of delivering the product, as a percentage of revenue. Illustration: if the revenue a plan attributes to Reports is $45 per account per month against the $49.63 cost, the gross margin on that line is (45 - 49.63) / 45 = -10.3%.
Trade-offs and pitfalls
Allocation is not causation: shared cost does not vanish when a feature is removed (the database stays), so a sunset decision should be based on avoidable cost, meaning the part that actually disappears, separate from the allocated share. Fixed drivers (equal split) are easy but misdirect; usage drivers cost metering effort, so start with the two largest pools. Do not re-cut drivers every month or the trend becomes noise; revisit quarterly. And do not let attribution turn into charging teams for things they cannot control.
Shared platform, R&D and G&A costs need to be allocated across several product lines so each has a contribution margin. Compare at least three allocation bases, say how you would build this into a model others can audit, and when each basis would mislead a decision.
Sample Answer
Direct answer
Allocate shared platform, R&D (research and development) and G&A (general and administrative: finance, legal, HR, executive) costs with a driver that matches how each cost pool is actually consumed, and show contribution margin at two levels: before shared costs (what the line earns over the costs it causes itself) and after allocation (what it earns once it carries its share of the common base). The three bases most teams start with are revenue share, headcount share and usage share. Each gives a different answer for the same business, so the model must make the basis explicit, reconcile to the general ledger (the company's master accounting record), and be read as a lens for decisions, not as a cash cost the line "causes".
Definitions used below. Contribution margin is revenue minus the costs a product line causes and would not incur without it (direct costs), usually shown in dollars and as a percentage of revenue. Shared costs are costs several lines benefit from and none triggers alone; a cost pool is one such bucket, for example Platform. An allocation base (or driver) is the measurable quantity used to split a pool: if a line accounts for 40% of the driver, it receives 40% of the pool.
Worked example (illustrative numbers, annual, $k = thousands of dollars)
Three product lines and three shared pools. All figures are invented for the example.
| Line | Revenue | Direct costs | Engineers | Platform API calls (millions per month) |
|---|---|---|---|---|
| Core app | 6,000 | 3,000 | 40 | 50 |
| Analytics | 2,500 | 1,200 | 10 | 40 |
| Mobile | 1,500 | 900 | 30 | 10 |
Shared pools: Platform 800 (hosting and shared services every line runs on), R&D 1,000, G&A 600, total 2,400. Contribution before shared costs: Core app 3,000, Analytics 1,300, Mobile 600 (4,900 in total, so the company earns 4,900 minus 2,400 = 2,500 after shared costs).
Reproducible script (plain Python 3, no dependencies; all inputs are pinned in the first lines):
lines = ["Core app", "Analytics", "Mobile"]
revenue = {"Core app": 6000, "Analytics": 2500, "Mobile": 1500} # $k per year
direct = {"Core app": 3000, "Analytics": 1200, "Mobile": 900} # $k per year, costs the line causes by itself
headcount = {"Core app": 40, "Analytics": 10, "Mobile": 30} # engineers
usage = {"Core app": 50, "Analytics": 40, "Mobile": 10} # millions of platform API calls per month
pools = {"Platform": 800, "R&D": 1000, "G&A": 600} # $k per year, total 2,400
def share(d):
t = sum(d.values())
return {k: v / t for k, v in d.items()}
def allocate(pool_total, driver):
s = share(driver)
return {k: pool_total * s[k] for k in lines}
shared_total = sum(pools.values())
bases = {"Revenue share": revenue, "Headcount share": headcount, "Usage share": usage}
print("Shared pool total ($k):", shared_total)
print("Contribution margin before shared costs ($k):",
{l: revenue[l] - direct[l] for l in lines})
for name, drv in bases.items():
a = allocate(shared_total, drv)
assert abs(sum(a.values()) - shared_total) < 1e-9
print(f"\n{name}: shares", {l: round(share(drv)[l]*100, 1) for l in lines})
for l in lines:
cm = revenue[l] - direct[l] - a[l]
print(f" {l:9s} allocated {a[l]:7.1f} margin {cm:7.1f} margin% {cm/revenue[l]*100:6.1f}")
# driver-based: each pool gets its own driver
drivers = {"Platform": usage, "R&D": headcount, "G&A": revenue}
tot = {l: 0.0 for l in lines}
print("\nPer-pool drivers (Platform=usage, R&D=headcount, G&A=revenue)")
for p, amt in pools.items():
a = allocate(amt, drivers[p])
print(" ", p, {l: round(a[l], 1) for l in lines})
for l in lines:
tot[l] += a[l]
for l in lines:
cm = revenue[l] - direct[l] - tot[l]
print(f" {l:9s} allocated {tot[l]:7.1f} margin {cm:7.1f} margin% {cm/revenue[l]*100:6.1f}")
print("check sum allocated:", round(sum(tot.values()), 6))
Output:
Shared pool total ($k): 2400
Contribution margin before shared costs ($k): {'Core app': 3000, 'Analytics': 1300, 'Mobile': 600}
Revenue share: shares {'Core app': 60.0, 'Analytics': 25.0, 'Mobile': 15.0}
Core app allocated 1440.0 margin 1560.0 margin% 26.0
Analytics allocated 600.0 margin 700.0 margin% 28.0
Mobile allocated 360.0 margin 240.0 margin% 16.0
Headcount share: shares {'Core app': 50.0, 'Analytics': 12.5, 'Mobile': 37.5}
Core app allocated 1200.0 margin 1800.0 margin% 30.0
Analytics allocated 300.0 margin 1000.0 margin% 40.0
Mobile allocated 900.0 margin -300.0 margin% -20.0
Usage share: shares {'Core app': 50.0, 'Analytics': 40.0, 'Mobile': 10.0}
Core app allocated 1200.0 margin 1800.0 margin% 30.0
Analytics allocated 960.0 margin 340.0 margin% 13.6
Mobile allocated 240.0 margin 360.0 margin% 24.0
Per-pool drivers (Platform=usage, R&D=headcount, G&A=revenue)
Platform {'Core app': 400.0, 'Analytics': 320.0, 'Mobile': 80.0}
R&D {'Core app': 500.0, 'Analytics': 125.0, 'Mobile': 375.0}
G&A {'Core app': 360.0, 'Analytics': 150.0, 'Mobile': 90.0}
Core app allocated 1260.0 margin 1740.0 margin% 29.0
Analytics allocated 595.0 margin 705.0 margin% 28.2
Mobile allocated 545.0 margin 55.0 margin% 3.7
check sum allocated: 2400.0
Reading the script in plain words: share() turns a driver such as engineers (40, 10, 30) into percentages of the total (50%, 12.5%, 37.5%); allocate() multiplies a pool by those percentages; the assert stops the run if the allocated pieces do not add back to the pool, so an error cannot hide; the loops print each line's allocation, margin in $k and margin as a percent of revenue. One cell by hand, Mobile under headcount share: 30 / 80 = 37.5%; 37.5% x 2,400 = 900 allocated; margin = revenue 1,500 - direct costs 900 - allocation 900 = -300, which is -300 / 1,500 = -20.0% of revenue.
Comparing the bases (whole 2,400 pool spread by one base)
| Basis | Core app margin % | Analytics margin % | Mobile margin % |
|---|---|---|---|
| Revenue share (60 / 25 / 15%) | 26.0 | 28.0 | 16.0 |
| Headcount share (50 / 12.5 / 37.5%) | 30.0 | 40.0 | -20.0 |
| Usage share (50 / 40 / 10%) | 30.0 | 13.6 | 24.0 |
| Driver per pool (usage for Platform, headcount for R&D, revenue for G&A) | 29.0 | 28.2 | 3.7 |
Same business, same 2,400 of shared cost, and Mobile reads as healthy (24.0%), marginal (3.7%, or 16.0% on revenue share) or loss-making (-20.0%) depending on the basis. Analytics ranges from 13.6% to 40.0%. That spread is the point of the exercise: the basis, not the economics, decides the headline.
When each basis misleads a decision
| Basis | What it is good for | When it misleads |
|---|---|---|
| Revenue share | Cheap, stable, easy to audit. Right for G&A, where overhead (finance, legal, HR, executive) loosely scales with business size. | Pushes margins toward one number: every line is charged the same 24% of its revenue (2,400 / 10,000), so each line's margin percentage is its direct margin percentage minus 24 points (Core app 50% to 26.0%, Analytics 52% to 28.0%, Mobile 40% to 16.0%). It charges a high-priced line for overhead it does not consume. Here Analytics earns 25% of revenue on 12.5% of the engineers, so revenue share charges it 600 where headcount share charges 300, and its margin reads 28.0% instead of 40.0%. A price rise on a line then raises its own cost allocation, which can make a good price move look worse. |
| Headcount share | Reasonable for R&D when engineers are the scarce resource and effort tracks team size. | Counts bodies, not value. A mature line with a large maintenance team (Mobile at 30 engineers) is charged heavily, giving -20.0% and a false "shut it down" signal. Distorts when contractors, shared engineers or part-time staff are counted inconsistently, and rewards a line for under-reporting its team. |
| Usage share | Best for the Platform pool (hosting and shared services such as login and storage), because compute, storage and API calls cause that cost. Gives lines a reason to economise. | Only as good as the metering. Calls are not equal (a cheap read vs an expensive query), internal or free traffic can inflate a line, and it says nothing about how R&D or G&A are consumed. A line with low usage but heavy platform engineering support looks cheap. |
| Driver per pool (recommended) | Matches each pool to its cause. | More inputs to maintain and defend. If a pool has no defensible driver, say so and leave it as an unallocated corporate line (a pool kept as its own line rather than split among product lines) instead of inventing one. |
Recommendation
Use a driver per pool, as in the last row: usage for Platform (800), headcount for R&D (1,000) and revenue for G&A (600). Report two numbers for every line: contribution before shared costs (Mobile: 600) and contribution after allocation (Mobile: 55). Use the first for "should we keep investing in or exit this line" (closing Mobile removes its 600 of contribution, and if the 2,400 of shared cost does not fall (it is not avoidable: it does not disappear when a line closes), the company margin drops from 2,500 to 1,900, so the remaining lines absorb the cost). Use the second for pricing floors (the minimum price that still covers a line's full costs), funding conversations and judging whether a line pays its way in the long run. Say which one a decision uses.
How to build it so others can audit it
- Inputs sheet or table, one row per fact. Revenue and direct costs come from the ledger with the account codes listed; drivers (headcount, API calls) carry a source system, an owner and an as-of date. No typed-in constants inside formulas.
- Pool table. Each shared pool maps to ledger accounts and names its driver and a one-sentence rationale ("Platform cost scales with calls served").
- One allocation step as a formula or script, as in the code above: share = line driver / total driver, allocation = pool x share. The reviewer can re-run it.
- Built-in checks that fail loudly. Allocated amounts must sum to each pool (the script asserts this), shares must sum to 100%, and total allocated plus unallocated must tie to the ledger's shared-cost total.
- Version and change log. Lock the period, record who changed a driver and why, and keep the previous basis alongside so a reader can see how much of a margin change came from a basis switch rather than the business.
- Sensitivity view. Show each line's margin under the alternative bases (the comparison table above) so a decision-maker sees the range, not a false precision.
- Explicit remainder. Costs with no honest driver (for example executive time or one-off legal) stay as "unallocated corporate", not smeared to make every line look fully loaded.
Trade-offs and pitfalls
- Fixed vs avoidable. Allocation does not make a fixed cost avoidable. Exiting a line usually frees only part of its allocation, and the rest is re-spread over the remaining lines. Judge exits on avoidable cost, not on allocated margin.
- Circular effects. If a basis is revenue and a team cuts price to gain volume, its allocation changes without any change in real cost. Keep drivers independent of the lever being judged where you can.
- Stale drivers. Headcount at year start can misstate a line that reorganised in Q3. Use average or period-end consistently and state which.
- Gaming. Teams tend to shift engineers or calls to the line with the lowest charge. Owners for driver data and spot audits fix this more than the formula does.
- Over-precision. Allocating to the dollar implies certainty the drivers do not have. Round, show the range across bases, and flag any decision that flips between bases as sensitive.
- Scope. This is about measuring each line's margin. Using that margin to set prices is a separate decision with its own demand and cost-to-serve (what it costs to deliver to each customer) questions.
Your organisation pays for many overlapping tools that do similar jobs. How would you decide which to keep, sequence the consolidation, and put a value on soft benefits such as analyst productivity?
Sample Answer
Direct answer
Score the overlapping BI (business intelligence) tools against a weighted rubric (cost, fit, integration effort, vendor support) after a pass/fail gate for must-haves like security and compliance, keep the best fit, and retire the others in order of lowest risk and nearest contract end, only after their critical content has been rebuilt. Put a number on soft benefits such as analyst productivity, but count them separately from hard savings and discount them for how many freed hours are really redeployed.
Deciding which to keep
- Inventory the overlap. Tool, owner, licence cost and renewal date, active users, and what each is actually used for.
- Gate first. Any tool that fails a must-have (data residency, security review, a required feature) is out regardless of score.
- Score the rest on the rubric below, with weights agreed with stakeholders before anyone sees the scores.
- Test the ranking. Change the weights and see whether the winner changes. If it does, the decision is a values question, not a data question, and it should be taken by the sponsor.
Worked example (illustrative scores and prices)
# Illustrative scoring of three overlapping BI tools. Scores 1-5 (5 = best). Weights sum to 1.
weights = {"cost": 0.30, "fit": 0.35, "integration_effort": 0.20, "vendor_support": 0.15}
tools = {
"Tool X": {"cost": 2, "fit": 5, "integration_effort": 4, "vendor_support": 4},
"Tool Y": {"cost": 4, "fit": 3, "integration_effort": 3, "vendor_support": 3},
"Tool Z": {"cost": 5, "fit": 2, "integration_effort": 2, "vendor_support": 2},
}
assert abs(sum(weights.values()) - 1) < 1e-9
for t, s in tools.items():
print(t, round(sum(weights[k] * s[k] for k in weights), 2))
# Savings: hard vs soft kept separate
annual_licence = {"Tool X": 120_000, "Tool Y": 60_000, "Tool Z": 40_000}
retire = ["Tool Y", "Tool Z"]
hard = sum(annual_licence[t] for t in retire)
migration_one_off = 45_000
# Soft: analysts stop maintaining duplicate dashboards
analysts, hours_saved_per_week, weeks, loaded_rate = 12, 2, 46, 70 # USD per hour, loaded
soft_gross = analysts * hours_saved_per_week * weeks * loaded_rate
realisation = 0.5 # only half of freed hours turn into other useful work (assumption)
soft = soft_gross * realisation
print("hard annual saving:", f"{hard:,}")
print("one-off migration:", f"{migration_one_off:,}", "payback months on hard only:", round(migration_one_off / hard * 12, 1))
print("soft gross:", f"{soft_gross:,.0f}", "soft counted:", f"{soft:,.0f}")
Tool X 3.75
Tool Y 3.3
Tool Z 2.9
hard annual saving: 100,000
one-off migration: 45,000 payback months on hard only: 5.4
soft gross: 77,280 soft counted: 38,640
Tool X wins at 3.75 even though it is the most expensive, because fit carries the heaviest weight (0.35). The decision is sensitive to that weight: with cost weighted 0.50, fit 0.20 and integration effort and vendor support 0.15 each, X scores 3.2 and Y and Z both score 3.5, and X loses first place. That is why the weights are agreed first and the sensitivity is shown.
Retiring Y and Z saves $60,000 + $40,000 = $100,000 a year in licences. With a $45,000 one-off migration, payback on hard savings alone is 45,000 / 100,000 x 12 = 5.4 months.
Valuing soft benefits
Analyst productivity is a real benefit and a soft one. The estimate: 12 analysts x 2 hours a week x 46 working weeks x $70 loaded hourly cost = $77,280 of time. I would not put all of that in the business case. Freed hours turn into value only if someone redeploys them, so I apply a realisation factor (here 50%, an assumption to be replaced with evidence from a pilot), which counts $38,640. Show it as a separate line below the hard savings, so the case stands on $100,000 of hard savings and the soft benefit is upside. Other soft benefits worth naming: fewer conflicting numbers, faster onboarding, lower security surface.
Sequencing the consolidation
- Align with contract dates. Note notice periods, because a missed renewal window costs another year.
- Start with the lowest-risk, lowest-use tool (Z here, on the assumption that the inventory shows it is the least used; the example scores give only its low fit and low price, not its usage) to prove the migration playbook.
- Rebuild before you retire. Identify the reports and data pipelines that matter, migrate them, and validate outputs side by side.
- Freeze new content in the retiring tool as soon as the decision is made.
- Run in parallel for one reporting cycle, then set a sunset date and keep an export for audit.
- Communicate early with the owners of each dashboard and train users on the retained tool.
Pitfalls
- Choosing on licence cost alone. The cheapest tool that fails the fit test creates shadow tooling.
- Counting soft savings as cash. Finance will discount it, so do it first and show how.
- Forgetting migration and retraining cost.
- Skipping the decision owner: weights are a judgement that someone senior must own.
Unlock Full Question Bank
Get access to all 45 Cost Optimization and Technology Financial Management interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.