Level 2 · Economics: Choices and Multipliers
Level 1 looked at markets from outside: supply and demand meet at an equilibrium price, and GDP adds up what a whole economy produces. This level looks inside both. The micro half follows one person making a choice. What does the choice really cost? Which affordable basket of goods does a shopper like best, and how does the answer move when prices or income change? How do you compare a euro today with a euro in three years, and how many hours is it worth working once taxes and benefits take their share? Every one of these is a search: list what is possible, score each possibility, keep the best.
The macro half asks why a whole economy's output settles where it does. One person's spending is another person's income, and income is spent again. Follow that circle and you get the Keynesian cross, where output equals planned spending, and the multiplier, which says how far output moves when spending changes. You will find it by algebra, watch it as rounds of re-spending and as output catching up year by year, and finally solve several trading economies at once.
The paths meet here more than once. Searching every affordable bundle is the exhaustive search of the Algorithms path, and finding a break-even interest rate by halving an interval is the bisection of Level 1, binary search on a number line. Choosing the best basket under a budget is an optimisation problem, the same kind the Machine Learning path solves when it minimises a loss. And output adjusting year by year towards its equilibrium is a first-order system of the Signals & Control path: the gap shrinks by the same factor every step.
What a choice really costs
You have one free evening and three ways to spend it. Each option has a benefit (what it is worth to you, in euros: the pay for tutoring, or the most you would pay to see a play) and a money cost (a ticket, a fee). Its net value is benefit minus cost. The opportunity cost of a choice is the net value of the best alternative you give up. Doing nothing is always possible and is worth 0, so an opportunity cost is never negative.
evening = {"tutoring": (35, 0), "theatre": (60, 22), "climbing": (25, 10)}
net = {name: benefit - cost for name, (benefit, cost) in evening.items()}
print(net)
choice = "climbing"
others = [v for name, v in net.items() if name != choice]
print("choosing", choice, "gives up", max(others + [0]))
{'tutoring': 35, 'theatre': 38, 'climbing': 15}
choosing climbing gives up 38
The climbing wall charges 10 euros, but that is not what climbing costs you: going climbing means not going to the theatre, worth 38 to you after paying for the ticket. A choice is worth making when its net value is at least its opportunity cost, and with a single option to pick, only the option with the highest net value passes that test. The money cost already sits inside every net value, so it is never added a second time.
The idea is everywhere in economics. The cost of a year at university includes the wage you could have earned instead; the cost of a field planted with wheat is the barley that could have grown there. Budgets in the next section put a number on it: along a budget line, one more unit of one good costs a fixed number of units of the other.
Common mistakes: comparing benefits instead of net values. Including the option itself among its alternatives. Forgetting the 0 for doing nothing, which matters when there is only one option or when every alternative loses money. Adding the money cost to the opportunity cost.
Practice: What Saturday Afternoon Costs
The budget set and the budget line
A shopper with income m buys two goods at prices p_x and p_y. A bundle (x, y) is how much of each she buys, and it is affordable when
All affordable bundles together are the budget set. The bundles that spend the income exactly, p_x x + p_y y = m, lie on the budget line. When goods come in whole units, both sets are finite, and two nested loops list them: the first good runs from 0 to m // px, and for each choice the money left decides how many of the second good still fit.
Ana has 14 euros for notebooks (4 euros each) and pens (3 euros each). A star marks the bundles on the budget line:
m, px, py = 14, 4, 3
for x in range(m // px + 1):
row = []
for y in range((m - px * x) // py + 1):
cost = px * x + py * y
row.append(f"({x},{y}){'*' if cost == m else ''}")
print(" ".join(row))
(0,0) (0,1) (0,2) (0,3) (0,4)
(1,0) (1,1) (1,2) (1,3)
(2,0) (2,1) (2,2)*
(3,0)
Buying nothing, (0, 0), is in the budget set; it always is. Only one bundle spends exactly 14 euros: with whole units, most budget lines pass between the grid points, and some pass through none. Press Run to see the budget set as a triangle of dots under the line:
m, px, py = 14, 4, 3
xs, ys = [], []
for x in range(m // px + 1):
for y in range((m - px * x) // py + 1):
xs.append(x)
ys.append(y)
plot(xs, ys, kind="scatter", label="affordable", xlabel="notebooks", ylabel="pens")
plot([0, m / px], [m / py, 0], label="budget line")
The slope of the budget line is -p_x / p_y: one more notebook costs 4 euros, which is 4/3 of a pen. That is the opportunity cost of the previous section measured in goods instead of euros.
Common mistakes: using < instead of <= (spending every coin is allowed). Starting a loop at 1, which loses the bundles with none of a good. Assuming the budget line always holds a whole-unit bundle. Running the inner loop to m // py for every x without testing the cost.
Practice: Tokens for the Fair
Utility: scoring bundles and searching for the best
Economists describe tastes with a utility function U(x, y): a number for every bundle, higher for a bundle the person prefers. Only the order matters. 10 \cdot U or U + 5 rank the bundles the same way, so they describe the same tastes.
In Python a utility function is an ordinary function, and a function can be passed to another function like any other value. Here a kiosk sells four fixed packs of sandwiches (x) and apples (y), and two people with different tastes choose among them. Both take the highest utility, and on a tie the cheaper pack:
def picnic_taste(x, y): # a sandwich is worth two apples
return 2 * x + y
def matching_taste(x, y): # only sandwich-and-apple pairs count
return min(x, y)
packs = [("A", 2, 1, 9), ("B", 1, 3, 8), ("C", 2, 2, 10), ("D", 3, 0, 9)] # name, x, y, price
for taste in (picnic_taste, matching_taste):
best = max(packs, key=lambda p: (taste(p[1], p[2]), -p[3]))
print(taste.__name__, best)
picnic_taste ('D', 3, 0, 9)
matching_taste ('C', 2, 2, 10)
The variable taste holds a function, and taste(p[1], p[2]) calls it. The key is a tuple, and Python compares tuples element by element: the first elements decide, and only on a tie do the second ones count. Negating the price makes "cheaper" mean "larger", so one max applies both rules. For picnic_taste, packs C and D both score 6, and D wins because it costs 9 instead of 10:
print((6, -9) > (6, -10), (6, -10) > (5, -8), (6, -9, 3) > (6, -9, 2))
True True True
If the whole key ties, max keeps the first item it met, so the answer depends on the order of the loop. When a problem states a tie rule, put every part of it in the key.
To choose from a whole budget set rather than a menu, combine this with the nested loops of the previous section: score every affordable bundle and keep the best key. Two warnings about what the search may find. More is not always better: with U = 6x - x^2 the fourth unit of x lowers utility, so the best bundle may leave money unspent. And with min-shaped tastes, a coin left over may buy nothing useful.
Aside (optional): do people maximise utility? Behavioural economists have documented many choices that no single utility function explains: people choose differently when the same options are described differently, and often favour the present more than they later wish they had. Others reply that the model is meant to predict how choices respond to prices and incomes on average, not to describe how anyone thinks, and that it does this reasonably well for many markets. Both views are active research programmes; the problems on this site always state the model to use.
Common mistakes: keeping the first bundle with the top score instead of applying the stated tie rule. Negating the wrong element of the key (higher utility should win, lower cost should win). Calling the utility function with the arguments swapped.
Practice: The Best Basket Money Can Buy
Marginal utility per coin
The marginal utility (MU) of a good is the extra utility one more unit adds. For most goods it shrinks as you have more of it: the first cup of cocoa on a cold day matters a lot, the fifth hardly at all. That is diminishing marginal utility. From a list of utilities, the marginal utilities are the differences:
cocoa = [0, 12, 21, 27, 30, 31] # utility of 0, 1, 2, ... cups
mu = [cocoa[k + 1] - cocoa[k] for k in range(len(cocoa) - 1)]
print(mu)
[12, 9, 6, 3, 1]
Marginal utility alone does not tell you what to buy next, because goods cost different amounts. A coin should go where it buys the most extra utility, so the yardstick is the marginal utility per coin, MU / p. With one cup of cocoa and one bun already bought:
from fractions import Fraction
prices = {"cocoa": 3, "bun": 2}
mus = {"cocoa": [12, 9, 6, 3, 1], "bun": [10, 8, 4]}
have = {"cocoa": 1, "bun": 1}
for good in prices:
k = have[good] # the next unit is unit number k + 1
print(good, "next MU", mus[good][k], "per coin", Fraction(mus[good][k], prices[good]))
cocoa next MU 9 per coin 3
bun next MU 8 per coin 4
The second cup of cocoa adds more utility (9 against 8), but the bun adds more per coin (4 against 3), so the next coins go to the bun. After that purchase the bun's next ratio drops to 4/2 = 2 and cocoa leads again: diminishing marginal utility spreads spending across goods. When the ratios differ, moving a coin from the good with the lower ratio to the one with the higher ratio raises utility. So at the best choice with smooth tastes the ratios are equal:
Ties between ratios are common with small whole numbers, and they need an exact comparison. Rounding can invent a tie that is not there, and floats computed along different routes can miss one that is. Fraction keeps ratios exact. Cross-multiplying works too: for positive prices, a / p > b / q exactly when a q > b p, which uses only whole numbers.
from fractions import Fraction
print(round(7 / 3, 2) == round(233 / 100, 2)) # rounding invents a tie
print(Fraction(7, 3) == Fraction(233, 100)) # exact: they differ
print((0.1 + 0.2) / 3 == 0.3 / 3) # floats miss a true tie
print(9 * 2 == 6 * 3) # 9/3 against 6/2, cross-multiplied
True
False
False
True
Common mistakes: comparing marginal utilities instead of marginal utility per coin. Using the marginal utility of the unit already bought instead of the next one. Rounding ratios before comparing them. Cross-multiplying the wrong pair (a * p instead of a * q).
Practice: Coin by Coin at the Market
Cobb-Douglas tastes: fixed spending shares
One family of utility functions appears everywhere in economics because its answer is so simple. Cobb-Douglas utility for two goods is
and for several goods U = \prod_i x_i^{a_i} with exponents that add up to 1. The result: the consumer spends the fixed share a_i of income on good i, whatever the prices, so
Here is why, using the rule of the previous section. For a small change in x, the marginal utility of x is a \, U / x (calculus gives this, and you can check it numerically by computing U(x + 0.001, y) - U(x, y) and dividing by 0.001); the marginal utility of y is (1 - a) \, U / y. Setting the two ratios per coin equal:
So spending on each good is proportional to its exponent. The two amounts of spending add up to m and the two exponents add up to 1, so the common value is m: spending on x is a m, and x = a m / p_x. A brute-force search along the budget line agrees:
a, m, px, py = 0.35, 200, 5, 2
def U(x, y):
return x ** a * y ** (1 - a)
best_x = max((i / 100 for i in range(1, 4000)), key=lambda x: U(x, (m - px * x) / py))
print("search:", best_x, " formula:", a * m / px)
print("spent on x:", round(px * best_x, 6), " on y:", round(m - px * best_x, 6))
search: 14.0 formula: 14.0
spent on x: 70.0 on y: 130.0
The formula makes demand easy to read. Doubling a good's price halves the amount bought of it and leaves the other good alone; doubling income doubles every quantity, and, because the exponents add up to 1, doubles the utility too:
a = 0.35
def demand(m, px, py):
return a * m / px, (1 - a) * m / py
for m, px, py in [(200, 5, 2), (200, 10, 2), (400, 5, 2)]:
x, y = demand(m, px, py)
print(m, px, py, "->", round(x, 2), round(y, 2), " U =", round(x ** a * y ** (1 - a), 4))
200 5 2 -> 14.0 65.0 U = 37.9786
200 10 2 -> 7.0 65.0 U = 29.7974
400 5 2 -> 28.0 130.0 U = 75.9571
Tastes are sometimes given as positive weights instead of shares, say 2 for books and 3 for food. x^2 y^3 is the fifth power of x^{0.4} y^{0.6}, so it ranks bundles the same way, but the demand formula needs shares that add up to 1: divide each weight by the total weight, a_i = w_i / \sum_j w_j. With several goods the utility is a running product: start at 1.0 and multiply in x ** a for each good.
Common mistakes: using raw weights as shares when they do not add up to 1. Dividing by the wrong good's price. Starting a running product at 0. Expecting a price change to move the demand for the other goods: with Cobb-Douglas tastes it does not.
Practice: Fixed Shares of the Pay Packet
Goods used together, goods used instead
Two extreme tastes have no smooth trade-off at all.
Perfect complements are only useful together, in a fixed proportion: U = \min(x / a, \; y / b). A pancake batter needs 3 cups of flour per egg, so a = 3, b = 1, and extra flour without eggs adds nothing. The best bundle buys whole "portions" of a units of x and b units of y. Writing x = a t and y = b t, the budget becomes t \, (a p_x + b p_y) = m:
a, b = 3, 1 # 3 cups of flour per egg
m, px, py = 88, 2, 5
t = m / (a * px + b * py) # portions the budget buys
x, y = a * t, b * t
print(x, y, "U =", min(x / a, y / b), " spent", px * x + py * y)
24.0 8.0 U = 8.0 spent 88.0
Perfect substitutes can replace each other at a fixed rate: U = a x + b y. A boiler burns either of two fuels, giving a or b hours of heat per litre. Along the budget line the utility changes in a straight line, so the best bundle is at one end, a corner solution: everything goes to the good with more utility per coin.
a, b = 5, 3
m, px, py = 40, 4, 2
for x in [0, 2.5, 5, 7.5, 10]:
y = (m - px * x) / py
print(x, y, a * x + b * y)
print("cross-multiplied:", a * py, "against", b * px)
0 20.0 60.0
2.5 15.0 57.5
5 10.0 55.0
7.5 5.0 52.5
10 0.0 50.0
cross-multiplied: 10 against 12
The first fuel gives more heat per litre (5 against 3) but less per euro (5/4 against 3/2), and the cross-multiplied comparison, 10 against 12, says the same with whole numbers: all the money goes on the second fuel. If the two ratios are exactly equal, every bundle on the budget line is equally good, and a problem must say which one to report.
Common mistakes: for complements, splitting the money equally, or in the ratio of the prices; the fixed proportion is in quantities. For substitutes, comparing a with b and ignoring the prices. Looking for an interior solution with the ratio rule of the previous sections: it only applies to smooth tastes.
Practice: Together or Instead
Better or worse off?
Prices rise by different amounts and your income rises too. Are you better off? In this model "better off" means a higher utility: compute the best bundle at last year's prices and income, then at this year's, and compare the two utilities.
With Cobb-Douglas tastes there is a shortcut. Every quantity is proportional to income, and the exponents add up to 1, so at fixed prices the best utility is proportional to income. That makes it easy to find the income that would exactly restore last year's utility at the new prices: scale any income by the ratio of the utilities.
Sami spends a share 0.25 of his income on fuel and the rest on other goods. Fuel went from 2 to 3 euros a unit, other goods stayed at 1, and his income went from 1200 to 1320, a 10 % raise:
a = 0.25 # share spent on fuel; the rest on other goods
def best_u(m, p_fuel, p_other):
fuel, other = a * m / p_fuel, (1 - a) * m / p_other
return fuel ** a * other ** (1 - a)
u_old = best_u(1200, 2, 1)
u_new = best_u(1320, 3, 1)
print("utility last year", round(u_old, 3), " this year", round(u_new, 3))
print("U / m at the new prices:", [round(best_u(m, 3, 1) / m, 6) for m in (1000, 1320, 2000)])
needed = 1320 * u_old / u_new
old_basket = 3 * (a * 1200 / 2) + 1 * ((1 - a) * 1200 / 1)
print("income needed", round(needed, 2), " last year's basket now costs", old_basket)
utility last year 575.049 this year 571.577
U / m at the new prices: [0.433013, 0.433013, 0.433013]
income needed 1328.02 last year's basket now costs 1350.0
Sami is worse off: 1320 is less than the 1328.02 he would need. Notice that the needed income is less than the cost of last year's basket at the new prices (1350). Buying the old basket again is one way to be exactly as well off, but not the cheapest: fuel became relatively dearer, so he can buy a little less fuel and a little more of everything else, and reach the old utility for less. A raise to 1330 would leave him better off, although it does not buy last year's basket.
This is the substitution bias of the fixed-basket price index of Level 1. A fixed basket assumes nobody switches, so it overstates how much income is needed to keep people as well off as before. For Cobb-Douglas tastes the exact factor is a weighted geometric mean of the price ratios, while the fixed basket gives the weighted arithmetic mean, which is never smaller:
print("exact:", round(1.5 ** 0.25 * 1.0 ** 0.75, 4), " fixed basket:", round(0.25 * 1.5 + 0.75 * 1.0, 4))
exact: 1.1067 fixed basket: 1.125
Common mistakes: comparing the new income with the old basket's cost, which can call someone worse off who is not. Using last year's quantities to compute this year's utility. Comparing two utilities with ==: two routes to the same number can differ in the last digit, so compare with a small tolerance.
Practice: Did the Raise Keep Up?
Money now and money later: present value
A euro today is worth more than a euro next year, because a euro today can earn interest. Put 100 euros in the bank at rate r (4 % is r = 0.04) and it grows to 100 \cdot (1 + r)^t after t years. Turned around, a payment of P euros t years from now is worth
today: its present value. Dividing by (1 + r)^t is called discounting.
r = 0.04
print([round(100 * (1 + r) ** t, 2) for t in range(4)]) # 100 today, left in the bank
print([round(100 / (1 + r) ** t, 2) for t in range(4)]) # 100 paid in year t, worth today
[100.0, 104.0, 108.16, 112.49]
[100.0, 96.15, 92.46, 88.9]
The present value of a stream of payments is the sum of the present values of its payments:
As a list, payments[t] is paid t years from now, so enumerate gives both the year and the amount, and the payment at index 0 (today) is divided by (1 + r)^0 = 1: it is not discounted. A training grant pays 250 today and three more payments at the ends of the next three years:
grant = [250, 400, 400, 600]
for r in (0, 0.03, 0.1):
pv = sum(p / (1 + r) ** t for t, p in enumerate(grant))
print(r, round(pv, 2))
0 1650.0
0.03 1564.47
0.1 1395.0
At rate 0 the present value is just the total. The higher the rate, the less the later payments are worth today, and the more a stream that pays late loses against one that pays early. Converting every offer to its present value puts offers with different timing on one scale, money today. A negative payment, such as a fee, is discounted in exactly the same way.
Common mistakes: discounting the payment at index 0, or starting the exponent at 1. Writing 5 % as r = 5. Multiplying by (1 + r)^t instead of dividing. Adding up payments from different years without discounting, which is only right when r = 0.
Practice: The Prize Today or Later
Net present value and the break-even rate
An investment is a stream of payments that starts with a cost. Its net present value (NPV) at rate r is the present value of the whole stream, with the cost today as a negative first entry:
A positive NPV means the investment earns more than putting the same money in the bank at rate r. A school weighs solar panels that cost 1200 (thousand euros) today and save 300 a year for five years:
solar = [-1200, 300, 300, 300, 300, 300]
def npv(flows, r):
return sum(c / (1 + r) ** t for t, c in enumerate(flows))
for r in (0, 0.05, 0.1, 0.15):
print(r, round(npv(solar, r), 2))
0 300.0
0.05 98.84
0.1 -62.76
0.15 -194.35
When only the first flow is negative, raising the rate shrinks every positive term, so the NPV falls steadily and crosses zero at most once. The rate where it is zero is the break-even rate (also called the internal rate of return): the panels pay when the school can borrow at any rate below it. Here it lies between 5 % and 10 %.
There is no formula for it in general, but a function that only falls (or only rises) crosses its target at most once, and Level 1's bisection traps the crossing, just as it found the price where excess demand is zero: keep two ends on opposite sides of the target, test the middle, keep the half that still contains the crossing. Each step halves the interval, so about 34 steps shrink [0, 1] below 10^{-10}. Here it is on a rising function with a known answer: at what rate do 2000 euros grow to 2600 in 5 years?
def grown(r):
return 2000 * (1 + r) ** 5
lo, hi = 0.0, 1.0 # grown(lo) is below 2600, grown(hi) is above
steps = 0
while hi - lo > 1e-10:
mid = (lo + hi) / 2
if grown(mid) < 2600:
lo = mid # the rate we want is above mid
else:
hi = mid
steps += 1
print(steps, round(lo, 8), round(1.3 ** (1 / 5) - 1, 8))
34 0.05387395 0.05387395
The search agrees with the exact answer 1.3^{1/5} - 1. An NPV falls as the rate rises, so the rule for which end to move flips: a positive NPV at mid means the zero lies above it. Before searching, check that the ends really differ in sign. If the NPV is already negative at rate 0 (the flows do not even add up to the cost), there is no break-even rate in the interval.
Common mistakes: moving the wrong end of the interval. Stopping after a fixed handful of halvings, which leaves the answer coarse. Searching without checking the signs at the ends. Discounting the cost paid today.
Practice: When the New Oven Pays
Hours of work, taxes and benefits
Choosing how many hours to work is a budget problem too, with time as the second good. Of the hours in a week, those not worked are leisure, and the pay for the hours worked, after taxes and benefits, is what can be spent: consumption. The interesting part is that the budget is no longer a straight line.
Income tax in bands charges each rate only on the part of earnings inside its band. With 0 % up to 200 euros a week, 20 % from 200 to 600 and 40 % above 600, earnings of 750 pay:
earnings = 750
tax = 0.0 * 200 + 0.2 * (600 - 200) + 0.4 * (earnings - 600)
print(tax, round(tax / earnings, 4))
140.0 0.1867
The marginal rate on the next euro is 40 %, but the average rate is under 19 %, because the lower bands still apply to the first 600. In general, band i taxes min(earnings, upper) - threshold when earnings are above its threshold, where upper is the next band's threshold (the last band has no upper end).
A benefit withdrawn at a taper pays a fixed amount and takes back a share of every euro earned until nothing is left: max(0, benefit - taper * earnings). While it lasts, an extra euro of earnings is cut by the tax rate and by the taper. Their sum is the effective marginal rate. With a wage of 10 euros, a flat 20 % tax and a benefit of 180 withdrawn at 50 cents per euro earned:
wage, rate, benefit, taper = 10, 0.2, 180, 0.5
prev = None
for h in range(0, 61, 10):
gross = wage * h
c = gross - rate * gross + max(0, benefit - taper * gross)
gain = "" if prev is None else f" +{round((c - prev) / 10, 2)} per extra hour"
print(f"{h} {c}{gain}")
prev = c
0 180.0
10 210.0 +3.0 per extra hour
20 240.0 +3.0 per extra hour
30 270.0 +3.0 per extra hour
40 320.0 +5.0 per extra hour
50 400.0 +8.0 per extra hour
60 480.0 +8.0 per extra hour
While the benefit lasts, each hour adds only 3 euros of consumption: the effective marginal rate is 20 % + 50 % = 70 %. Once the benefit is used up (at 360 euros of earnings, which the step from 30 to 40 hours crosses) each hour adds 8. A worker who values leisure may then find that working little or nothing is the best choice, an effect called the welfare trap.
To choose the hours, score each whole number of hours with a utility of consumption and leisure, such as the Cobb-Douglas U = c^{a} \cdot \text{leisure}^{1 - a}, and keep the best, exactly the search of the utility section. When two choices score the same in exact arithmetic, floats may still differ in the last digit, so a problem will state a margin: treat utilities within a tiny relative tolerance of the best as tied.
Common mistakes: taxing all earnings at the top band's rate. Letting the benefit go below 0. Taxing the benefit when the model says it is untaxed. Forgetting that the effective marginal rate changes at every band threshold and where the benefit runs out.
Practice: Hours Worth Working
Consumption and saving
Now the whole economy. As Level 1's GDP accounting showed, the value of everything produced, output Y, is also everyone's income: every euro paid for goods ends up as someone's wages, rent or profit. Households pay taxes T out of it, which leaves disposable income Y - T, and they spend part of that according to a consumption function:
c_0 is autonomous consumption, what households spend even with no disposable income (by drawing on savings), and c_1 is the marginal propensity to consume (MPC): of each extra euro of disposable income they spend c_1 and save the rest, 1 - c_1, the marginal propensity to save (MPS). Household saving is the disposable income not consumed:
c0, c1, T = 30, 0.6, 50
for Y in (50, 100, 150, 200, 300):
yd = Y - T
C = c0 + c1 * yd
print(Y, yd, round(C, 2), round(yd - C, 2))
50 0 30.0 -30.0
100 50 60.0 -10.0
150 100 90.0 10.0
200 150 120.0 30.0
300 250 180.0 70.0
At low incomes saving is negative: households spend more than their disposable income. Each extra 50 of disposable income adds 0.6 \cdot 50 = 30 to consumption and 0.4 \cdot 50 = 20 to saving, so C and S always add up to Y - T. Substituting C into the saving formula gives S = -c_0 + (1 - c_1)(Y - T), a straight line with slope equal to the MPS.
Common mistakes: applying c_1 to Y instead of Y - T. Computing saving as Y - C, which counts the tax as saving. Writing the MPC as a percentage (60) instead of a fraction (0.6).
Practice: Spend or Save on Pell Island
The Keynesian cross: where output settles
Buyers plan to spend on consumption, investment and government purchases. Planned spending depends on output through consumption:
(an open economy adds net exports X - M, the last terms of Level 1's GDP identity). Firms decide how much to produce before they know what will be bought. If they produce more than buyers plan to spend, the unsold goods stay on the shelves: inventories rise unplanned by Y - PE(Y). If they produce less, shelves are emptied and the change is negative. Output is in equilibrium when it equals planned spending, Y = PE(Y), so that nobody is surprised.
Solving is the algebra of collecting the Y terms on one side:
The always-useful check: plug Y^* back in and confirm that PE(Y^*) = Y^*.
c0, c1, T, I, G = 20, 0.6, 50, 70, 90
def planned(Y):
return c0 + c1 * (Y - T) + I + G
Y_star = (c0 - c1 * T + I + G) / (1 - c1)
print("Y* =", round(Y_star, 6), " PE(Y*) =", round(planned(Y_star), 6))
for Y in (300, 450):
print("produce", Y, "-> planned", round(planned(Y), 2), " unplanned inventory change", round(Y - planned(Y), 2))
Y* = 375.0 PE(Y*) = 375.0
produce 300 -> planned 330.0 unplanned inventory change -30.0
produce 450 -> planned 420.0 unplanned inventory change 30.0
Producing 450, firms see 30 of goods pile up and cut production; producing 300, they see shelves emptied by 30 and produce more. Both push output towards 375. Press Run to see the cross: the line PE = Y at 45 degrees, and planned spending, flatter because each extra euro of income adds only c_1 euros of spending.
c0, c1, T, I, G = 20, 0.6, 50, 70, 90
ys = list(range(0, 701, 50))
plot(ys, ys, label="output Y", xlabel="output Y", ylabel="spending")
plot(ys, [c0 + c1 * (y - T) + I + G for y in ys], label="planned spending PE(Y)")
Common mistakes: subtracting the tax from output in full (-T) instead of through consumption (-c_1 T). Getting the sign of the inventory change backwards: output above planned spending means unsold goods, a positive change. Dividing by c_1 instead of 1 - c_1.
Practice: Shelves Filling, Shelves Emptying
The multiplier, two ways
If the government buys 10 more, how much does equilibrium output rise? Not by 10. Solve the model twice, before and after, and subtract:
def equilibrium(c0, c1, T, I, G):
return (c0 - c1 * T + I + G) / (1 - c1)
base = equilibrium(20, 0.6, 50, 70, 90)
print("G + 10: ", round(equilibrium(20, 0.6, 50, 70, 100) - base, 6))
print("I + 10: ", round(equilibrium(20, 0.6, 50, 80, 90) - base, 6))
print("c0 + 10:", round(equilibrium(30, 0.6, 50, 70, 90) - base, 6))
print("1 / (1 - c1) =", round(1 / (1 - 0.6), 6))
G + 10: 25.0
I + 10: 25.0
c0 + 10: 25.0
1 / (1 - c1) = 2.5
Output rises by 25, two and a half times the new spending. The algebra shows why. Write the equilibrium condition before and after a change \Delta G and subtract; everything that did not change cancels:
1 / (1 - c_1) is the spending multiplier. Output must rise by the new spending plus the extra consumption that the higher income itself causes. G, I and c_0 enter the equation in exactly the same way, so they share one multiplier: in this model a euro of autonomous spending has the same effect whoever spends it. The levels of c_0, T, I and G do not appear in the multiplier at all, so a change in output can be predicted without knowing them.
The second way tells the same story in rounds. The government spends 10; that is income for the sellers, who spend 0.6 \cdot 10 = 6; that is income for others, who spend 3.6; and so on:
c1, injection = 0.6, 10
rounds = [injection * c1 ** k for k in range(8)]
print([round(x, 3) for x in rounds])
print(round(sum(rounds), 3), "after 8 rounds; all rounds:", round(injection / (1 - c1), 3))
[10.0, 6.0, 3.6, 2.16, 1.296, 0.778, 0.467, 0.28]
24.58 after 8 rounds; all rounds: 25.0
The rounds form a geometric series. Its sum S = 1 + c + c^2 + \dots satisfies S - cS = 1 (multiply by c and every term but the first reappears), so
which is the multiplier again. After n rounds the partial sum is (1 - c^n) / (1 - c), so the total creeps up towards the full sum without reaching it. A while loop can follow the rounds until some stopping rule says enough; here, until the running total is within 0.1 of the full sum:
c1, injection = 0.6, 10
full = injection / (1 - c1)
amount, total, n = injection, 0.0, 0
while full - total > 0.1:
total += amount
amount *= c1
n += 1
print(n, round(total, 4), round(full - total, 4))
11 24.9093 0.0907
With a threshold, decide on purpose what happens at the boundary. "Stop at the first round below the threshold" means a round exactly at the threshold is still counted, which is while amount >= threshold; writing > drops it. And if the very first amount already fails the test, the loop body never runs and nothing is counted.
Aside (optional): how big are real multipliers? This model's multiplier, 1/(1 - c_1), is large whenever households spend most of each extra euro. Economists disagree about how large multipliers are in real economies, and estimates from data vary widely between studies, countries and periods. Some stress forces that can make them larger than simple models suggest, such as idle workers and machines in a slump, or households that spend every extra euro because they cannot borrow. Others stress forces this model leaves out that make them smaller, such as interest rates rising when the government borrows, people expecting higher taxes later, and spending that leaks abroad. Much current work argues that the size depends on circumstances, such as the state of the economy and how the central bank responds. The problems here always ask what follows inside a stated model.
Common mistakes: using 1 / c_1 or 1 - c_1 instead of 1 / (1 - c_1). Reporting each change's own effect when the question asks for the running total since the start. Counting the first round below a threshold, or dropping a round exactly equal to it.
Practice: Ripples From the New Quay, The Fair Comes to Dunmore
Taxes, balanced budgets, and running the multiplier backwards
A tax rise works through consumption only. Raising T by one euro cuts disposable income by one euro, but households cut spending by only c_1 of it and pay the rest out of saving. Subtracting the equilibrium conditions before and after:
So the tax multiplier, the change in output per euro of tax rise, is -c_1 / (1 - c_1): negative, and smaller in size than the spending multiplier. If purchases and taxes rise by the same amount, the budget balance T - G is unchanged, and the two effects add up to
the balanced-budget multiplier: in this model, a tax-financed rise in purchases raises output by exactly the amount spent.
def equilibrium(c0, c1, T, I, G):
return (c0 - c1 * T + I + G) / (1 - c1)
base = equilibrium(20, 0.6, 50, 70, 90)
tax_up = equilibrium(20, 0.6, 60, 70, 90) - base # T up by 10
both_up = equilibrium(20, 0.6, 60, 70, 100) - base # T and G up by 10
print(round(tax_up / 10, 6), round(-0.6 / (1 - 0.6), 6), round(both_up / 10, 6))
-1.5 -1.5 1.0
A multiplier answers "if we change this lever, how far does output move?". Run backwards, it answers "how far must we move the lever to change output by a given amount?": divide the required change by the multiplier. With c_1 = 0.6, raising output by 60 takes:
c1 = 0.6
gap = 60 # equilibrium output should rise by 60
k_G, k_T = 1 / (1 - c1), -c1 / (1 - c1)
print("change G by", round(gap / k_G, 6))
print("change T by", round(gap / k_T, 6))
print("change both by", round(gap / (k_G + k_T), 6))
change G by 24.0
change T by -40.0
change both by 60.0
A tax cut of 40, or 24 of extra purchases, or 60 of both together. Inverse problems can be impossible, and the model says when. If a lever's multiplier is 0 it moves nothing: with c_1 = 0 households spend none of a tax cut, and dividing by the multiplier would divide by zero. And purchases or taxes cannot end up below 0, so a tax cut bigger than the taxes collected is not available. Check every answer by putting the new lever value back into the equilibrium formula.
Common mistakes: giving the tax multiplier a positive sign. Using -1 / (1 - c_1) for taxes, which makes a balanced budget change do nothing. Mixing up the sign of the balance: here T - G, so a deficit is negative. Dividing by a multiplier that can be 0.
Practice: Spend It or Hand It Back, Three Levers for the Treasury
Leakages: proportional taxes and imports
In the simple model every extra euro of income becomes c_1 euros of spending on home goods. Real economies have more leakages, parts of each extra euro of income that do not come back as demand for home output. Two common ones:
- a proportional tax: T = T_0 + t Y, a fixed part plus a share t of income;
- imports: M = m Y, a share m of income spent on foreign goods, which appear with a minus sign in planned spending PE = C + I + G + X - M.
Substituting T into consumption gives C = c_0 - c_1 T_0 + c_1 (1 - t) Y. Collecting the Y terms in Y = C + I + G + X - mY:
Of each extra euro of income, t goes in tax, households spend c_1 of the rest, and m of the whole euro goes abroad: only c_1 (1 - t) - m comes back as demand for home goods.
c0, c1, T0, t, m = 40, 0.75, 10, 0.2, 0.15
I, G, X = 60, 100, 80
def planned(Y):
T = T0 + t * Y
C = c0 + c1 * (Y - T)
return C + I + G + X - m * Y
k = 1 / (1 - c1 * (1 - t) + m)
Y = k * (c0 - c1 * T0 + I + G + X)
print("multiplier", round(k, 4), " without leakages", round(1 / (1 - c1), 4))
print("Y* =", round(Y, 4), " PE(Y*) =", round(planned(Y), 4))
multiplier 1.8182 without leakages 4.0
Y* = 495.4545 PE(Y*) = 495.4545
The leakages cut the multiplier from 4 to about 1.8. With the equilibrium output in hand, tax revenue T_0 + tY and net exports X - mY follow by substitution.
Common mistakes: applying t to consumption instead of to income. Writing c_1 (1 - t - m): imports are a share of income, not of disposable income, so m sits outside the parentheses. Forgetting that the fixed part T_0 lowers consumption by c_1 T_0.
Practice: Leaks in the Spending Stream
Output catching up, year by year
The equilibrium says where output settles, not how it gets there. A simple story of the path: firms cannot see demand in advance, so each year they produce what buyers planned to spend the year before,
Subtract the equilibrium condition Y^* = PE(Y^*) and everything but one term cancels:
Each year the gap to equilibrium shrinks to c_1 times what it was. This is the first-order system of the Signals & Control path, and the same shape as Level 1's auctioneer homing in on the market price: a value that closes a fixed fraction of its distance to the steady state every step.
c0, c1, T, I, G = 20, 0.6, 50, 70, 90
Y_star = (c0 - c1 * T + I + G) / (1 - c1)
Y = 200.0
path = [Y]
for year in range(1, 7):
Y = c0 + c1 * (Y - T) + I + G
path.append(Y)
print([round(y, 2) for y in path])
print([round(y - Y_star, 2) for y in path])
[200.0, 270.0, 312.0, 337.2, 352.32, 361.39, 366.84]
[-175.0, -105.0, -63.0, -37.8, -22.68, -13.61, -8.16]
The gap goes -175, -105, -63, \dots, multiplied by 0.6 each year. Press Run to watch the path approach Y^* = 375:
c0, c1, T, I, G = 20, 0.6, 50, 70, 90
Y, path = 200.0, []
for year in range(15):
path.append(Y)
Y = c0 + c1 * (Y - T) + I + G
plot(path, label="output", xlabel="year", ylabel="Y")
plot([375] * 15, label="equilibrium")
To stop a simulation "once output is close to equilibrium", measure the distance to Y^*, not the change from last year. The yearly step is (1 - c_1) times the gap, so when c_1 is close to 1 the steps are tiny while the gap is still large: with c_1 = 0.95 and a gap of 100, output moves only 5 in a year. And check the starting year before the first update: if it is already close enough, zero years are needed.
Common mistakes: stopping when two successive outputs are close. Updating before checking the starting year. Getting the boundary of the tolerance wrong: "within tol" includes a gap exactly equal to tol.
Practice: Producing Last Year's Demand
Saving in equilibrium: the paradox of thrift
Income has only three uses: it is consumed, saved or paid in tax, so Y = C + S + T. In equilibrium output also equals planned spending, Y = C + I + G. Set the two equal and C cancels:
In equilibrium, household saving equals investment plus the government's deficit. Now suppose every household decides to save more by cutting autonomous consumption by 10:
def outcome(c0, c1, T, I, G):
Y = (c0 - c1 * T + I + G) / (1 - c1)
S = (Y - T) - (c0 + c1 * (Y - T))
return round(Y, 6), round(S, 6)
print("before:", outcome(20, 0.6, 50, 70, 90))
print("after: ", outcome(10, 0.6, 50, 70, 90))
print("I + G - T =", 70 + 90 - 50)
before: (375.0, 110.0)
after: (350.0, 110.0)
I + G - T = 110
Output falls by 25 (the multiplier at work on a cut of 10), and at the lower income households end up saving exactly what they saved before. Each household alone could save more by spending less, but one household's spending is another's income, and when all of them cut back, incomes fall until saving is pulled back to I + G - T. In this model that is the paradox of thrift.
If investment rises with output, I = I_0 + d \, Y with d \ge 0 (firms invest more when business is good), the identity still holds, but now lower output means lower investment, so saving ends up lower than before. Collecting the Y terms gives Y (1 - c_1 - d) = c_0 - c_1 T + I_0 + G, which needs c_1 + d < 1 for a sensible equilibrium.
Common mistakes: computing saving after the change with the old c_0. Using Y - C as saving, which includes the tax. Forgetting the d Y part of investment when collecting the Y terms. Using the identity S = I + G - T to check a result is a good habit; using it outside equilibrium is not.
Practice: Everyone Tightens Their Belts
Many unknowns at once: elimination
When several economies trade, or several markets affect each other, no unknown can be found alone. Each equation involves the others, but if every relation is linear, the unknowns solve a linear system: one equation per unknown, all unknowns on the left, constants on the right.
An example from Level 1's supply and demand: tea, coffee and milk prices p_0, p_1, p_2. Each good's demand depends on all three prices (tea and coffee are substitutes, milk is used with both), and setting each demand equal to its supply and moving every price to the left gives
Write it as an augmented matrix: one row per equation, its coefficients followed by its right side. Gaussian elimination subtracts multiples of row 0 from the rows below to clear their first column, then multiples of row 1 to clear the second column below it, and so on, until the matrix is triangular. Back substitution then solves the last row (one unknown), puts that value into the row above, and works upwards.
def solve(A):
A = [row[:] for row in A] # work on a copy
n = len(A)
for col in range(n):
for r in range(col + 1, n):
f = A[r][col] / A[col][col]
for k in range(col, n + 1):
A[r][k] -= f * A[col][k]
x = [0.0] * n
for r in range(n - 1, -1, -1):
x[r] = (A[r][n] - sum(A[r][k] * x[k] for k in range(r + 1, n))) / A[r][r]
return x
A = [[6, -1, 1, 73],
[-1, 8, 1, 70],
[0.5, 0.5, 4, 50.5]]
p = solve(A)
print([round(v, 6) for v in p])
print(all(abs(sum(row[k] * p[k] for k in range(3)) - row[3]) < 1e-9 for row in A))
[12.0, 9.0, 10.0]
True
The last line plugs the answer back into every equation: always do this. Elimination divides by the diagonal entry A[col][col] (the pivot), so it fails if a pivot is 0. Swapping in the row with the largest entry in that column first (partial pivoting) avoids that. When the size of each diagonal entry is larger than the sizes of the other entries in its column put together, as in this system, no pivot can become 0.
Trade data often comes as a shares matrix: shares[i][j] is the fraction of country i's purchases abroad that go to country j. Each row adds up to 1, because every euro country i spends abroad goes somewhere. What country j sells is then a sum down column j: every buyer i's spending times the share it sends to j.
spend_abroad = [100, 40, 60] # what each town spends in the other towns
shares = [[0, 0.6, 0.4], # shares[i][j]: fraction of town i's spending that goes to town j
[0.3, 0, 0.7],
[1, 0, 0]]
receives = [round(sum(shares[i][j] * spend_abroad[i] for i in range(3)), 6) for j in range(3)]
print(receives, sum(receives))
[72.0, 60.0, 68.0] 200.0
What the towns spend and what they receive both add up to 200: one town's imports are the others' exports. When the spending abroad depends on each town's income (a share of an unknown output), each such term is an unknown multiplied by a coefficient. Move it to the left side of the row it belongs to, and the system is ready for elimination.
Common mistakes: reading shares[j][i] when shares[i][j] is meant: a seller's receipts read a column. Forgetting to move an unknown term to the left before building the matrix. Clearing a column with a row that has already been used, or changing the caller's lists instead of a copy.
Practice: Ports Around the Inner Sea
Practice
These problems are required on this path at this level; the path adds one problem from each of the level's topics, picked for you and kept.
- The Best Basket Money Can Buy
- Fixed Shares of the Pay Packet
- The Prize Today or Later
- Shelves Filling, Shelves Emptying
- The Fair Comes to Dunmore
- Spend It or Hand It Back
Checklist
You can now:
- Find the opportunity cost of a choice as the net value of the best alternative, with doing nothing worth 0.
- List a budget set of whole-unit bundles, and tell it from the budget line.
- Pass a utility function as an argument, search every affordable bundle for the best one, and apply a tie rule with a tuple key.
- Compare goods by marginal utility per coin, exactly, with
Fractionor by cross-multiplying. - Derive and use Cobb-Douglas demand x_i = a_i m / p_i, and solve perfect complements and perfect substitutes.
- Judge whether a price and income change leaves someone better or worse off, find the income that compensates, and explain why the old basket overstates it.
- Discount payments to present values, compute an NPV, and find a break-even rate by bisection.
- Turn hours of work into consumption through tax bands and a tapered benefit, and read the effective marginal rate.
- Solve the Keynesian cross, read unplanned inventory changes, and derive the spending, tax and balanced-budget multipliers, forwards and backwards.
- Sum a geometric series, add leakages to the multiplier, simulate output year by year, explain the paradox of thrift, and solve several linked equilibria by Gaussian elimination.