The three statements as one system
The linkage questions — "depreciation goes up $10, walk me through it" — all have circulating memorised answers, and the memorised answer breaks the moment the tax rate changes. This lesson builds the system that derives them, proves the classic answer is two expressions rather than three numbers, and shows the pair of scenarios candidates reliably swap.
What you'll be able to do
- Derive the cash flow statement and balance sheet from income-statement changes rather than recalling them
- State the classic depreciation answer as a function of the tax rate, and explain why cash rises at all
- Separate the two confusable cases — an expense that moves no cash, and cash that is not an expense
- Use the balancing identity as the check that catches a mis-specified answer mid-sentence
Before this: which-desk-are-you-interviewing-for
"Depreciation increases by $10. Walk me through the three statements."
This is the most asked technical question in finance interviews, and there are dozens of written answers to it online, all of them correct, all of them useless. They are useless because the answer depends on the tax rate, and the versions you find quote different rates — one walkthrough uses 25% and gets net income down $7.50 with cash up $2.50; another uses 20% and gets net income down $8 with cash up $2. A candidate who memorised one of them and is asked the question at 21% has nothing.
The answer to that question is not three numbers. It is two expressions, and this lesson derives them.
The identity that makes the third statement redundant
The three statements are not three documents that happen to be filed together. They are one system held together by one identity — Assets = Liabilities + Equity — after every transaction, always. That is not a checksum applied at the end. It is the reason you can be given changes to two statements and derive the third — which is exactly the task the linkage question sets.
The links, stated as the mechanics rather than as a diagram:
- Net income from the bottom of the income statement is the top line of the cash flow statement and, via retained earnings, the change in equity.
- Non-cash charges — depreciation, amortisation, write-downs, stock compensation — reduced net income without moving cash, so cash flow from operations adds them back.
- Working capital corrects for the difference between booking and paying. Receivables rising means revenue recognised but not collected, so cash flow is lower than net income. Payables rising means expenses booked but not paid, so cash flow is higher.
- Ending cash from the bottom of the cash flow statement is the cash line on the balance sheet.
Nine minutes covering the same $10 depreciation walk this lesson derives, in the order an interviewer expects to hear it — income statement, then cash flow, then the balance sheet connections. Watch 03:44 to 07:15 and then build it yourself below; the point of doing both is that the video gives you the script and the model gives you the reason the script is right.
Jump to the part you need
Building it instead of reciting it
The cell below implements that direction of travel as a function. Each scenario states only what changed at source; net income, all three cash flow sections, and the balance sheet are derived, and then the identity is checked. If a scenario fails the check, the scenario was mis-specified — and that failure is precisely the error an interviewer is listening for when they ask "and does the balance sheet balance?"
One assumption is stated in the header, and you should state it out loud too: taxes are booked and paid at the statutory rate in the same period. The note after the cell covers what that hides.
// The three statements are not three documents. They are one system with one
// identity holding it together: Assets = Liabilities + Equity, after every
// transaction, always. So instead of memorising "depreciation up $10" as a
// story, build the system once and let it derive the story.
//
// Each scenario below states only what changed AT SOURCE. Everything else --
// net income, the cash flow statement, the balance sheet -- is derived, and
// then the identity is checked. If a scenario fails the check, it was
// mis-specified: that failure is the exact error an interviewer is listening
// for when they ask "and does the balance sheet balance?"
//
// Assumption stated up front, as you must state it out loud: taxes are booked
// AND paid at the statutory rate in the same period. See the note below the
// cell for what that hides.
const TAX = 0.25;
/** Derive all three statements from source deltas. Nothing here is a lookup. */
function statements(s, t = TAX) {
const g = (k) => s[k] || 0;
// ── Income statement ──────────────────────────────────────────────────
const pretax =
g("revenue") - g("cashOpex") - g("depreciation") - g("writedown") - g("interest");
const tax = pretax * t;
const netIncome = pretax - tax;
// ── Cash flow statement ───────────────────────────────────────────────
// Start at net income, add back what moved no cash, then correct for the
// working-capital accounts, because an accrual is a promise, not a payment.
const cfo =
netIncome +
g("depreciation") +
g("writedown") -
g("dAR") -
g("dInventory") +
g("dAP") +
g("dAccrued");
const cfi = -g("capex");
const cff = g("dDebt") + g("dStock");
const dCash = cfo + cfi + cff;
// ── Balance sheet ─────────────────────────────────────────────────────
const dPPE = g("capex") - g("depreciation");
const dInventoryBS = g("dInventory") - g("writedown");
const dAssets = dCash + g("dAR") + dInventoryBS + dPPE;
const dLiabilities = g("dAP") + g("dAccrued") + g("dDebt");
const dEquity = g("dStock") + netIncome; // retained earnings; no dividends
return {
netIncome, cfo, cfi, cff, dCash,
dAssets, dLiabilities, dEquity,
gap: dAssets - (dLiabilities + dEquity),
};
}
const SCENARIOS = [
["Depreciation +10", { depreciation: 10 }],
["Write down $10 of inventory", { writedown: 10 }],
["Accrue $10 of expense, unpaid",{ cashOpex: 10, dAccrued: 10 }],
["Buy $10 of inventory for cash",{ dInventory: 10 }],
["Spend $10 on capex", { capex: 10 }],
["Issue $100 of debt at 5%", { dDebt: 100, interest: 5 }],
["Buy back $50 of stock", { dStock: -50 }],
];
const n = (x) => (Math.round(x * 100) / 100).toFixed(2).padStart(8);
console.log("tax rate " + (TAX * 100).toFixed(0) + "% (every number below follows from it)");
console.log("");
console.log("scenario net inc CFO CFI CFF cash assets L + E balances");
for (const [name, s] of SCENARIOS) {
const r = statements(s);
console.log(
" " + name.padEnd(32),
n(r.netIncome), n(r.cfo), n(r.cfi), n(r.cff), n(r.dCash),
n(r.dAssets), n(r.dLiabilities + r.dEquity),
Math.abs(r.gap) < 1e-9 ? " yes" : " NO (" + r.gap.toFixed(2) + ")"
);
}
const broken = SCENARIOS.filter(([, s]) => Math.abs(statements(s).gap) > 1e-9);
console.log("");
console.log(broken.length === 0
? "All " + SCENARIOS.length + " balance. The identity is not a checksum you apply at"
: broken.length + " DO NOT BALANCE -- the scenario is mis-specified.");
console.log("the end; it is the thing that makes the third number derivable from");
console.log("the first two.");
console.log("");
// ── The two rows that look identical and are not ──────────────────────────
const dep = statements({ depreciation: 10 });
const inv = statements({ dInventory: 10 });
const sgn = (x) => (x >= 0 ? "+" : "") + x.toFixed(2);
console.log("Rows 1 and 4 are the pair worth staring at.");
console.log(" Depreciation +10: income " + sgn(dep.netIncome).padStart(6) +
" cash " + sgn(dep.dCash).padStart(6) +
" assets " + sgn(dep.dAssets).padStart(6));
console.log(" Inventory +10: income " + sgn(inv.netIncome).padStart(6) +
" cash " + sgn(inv.dCash).padStart(6) +
" assets " + sgn(inv.dAssets).padStart(6));
console.log("One is an expense that moves no cash. The other is cash that is not");
console.log("an expense. Candidates who have memorised answers rather than the");
console.log("system reliably swap them.");
console.log("");
// ── The answer is a function of the tax rate, not a number ────────────────
console.log("Same $10 of depreciation, four tax rates:");
console.log(" tax net income cash assets RE check: -10*(1-t) and +10*t");
for (const t of [0, 0.21, 0.25, 0.40]) {
const r = statements({ depreciation: 10 }, t);
const okNI = Math.abs(r.netIncome - -10 * (1 - t)) < 1e-9;
const okCash = Math.abs(r.dCash - 10 * t) < 1e-9;
console.log(
" " + (t * 100).toFixed(0).padStart(3) + "%",
n(r.netIncome), n(r.dCash), n(r.dAssets), n(r.dEquity),
" " + (okNI && okCash ? "both hold" : "FAILED")
);
}
console.log("");
console.log("So the whole of the classic answer is two expressions:");
console.log(" net income moves by -expense x (1 - t)");
console.log(" cash moves by +expense x t <- the tax shield");
console.log("At t = 0 the tax shield is zero and cash does not move at all, which");
console.log("is the fastest way to prove to yourself that the cash increase was");
console.log("never about the expense. It was always about the deduction.");
# The three statements are not three documents. They are one system with one
# identity holding it together: Assets = Liabilities + Equity, after every
# transaction, always. So instead of memorising "depreciation up $10" as a
# story, build the system once and let it derive the story.
#
# Each scenario below states only what changed AT SOURCE. Everything else --
# net income, the cash flow statement, the balance sheet -- is derived, and
# then the identity is checked. If a scenario fails the check, it was
# mis-specified: that failure is the exact error an interviewer is listening
# for when they ask "and does the balance sheet balance?"
#
# Assumption stated up front, as you must state it out loud: taxes are booked
# AND paid at the statutory rate in the same period. See the note below the
# cell for what that hides.
TAX = 0.25
def statements(s, t=TAX):
"""Derive all three statements from source deltas. Nothing here is a lookup."""
g = s.get # `dict.get` with a default is Python's version of `s[k] || 0`
# -- Income statement --------------------------------------------------
pretax = (
g("revenue", 0) - g("cash_opex", 0) - g("depreciation", 0)
- g("writedown", 0) - g("interest", 0)
)
tax = pretax * t
net_income = pretax - tax
# -- Cash flow statement -----------------------------------------------
# Start at net income, add back what moved no cash, then correct for the
# working-capital accounts, because an accrual is a promise, not a payment.
cfo = (
net_income
+ g("depreciation", 0)
+ g("writedown", 0)
- g("d_ar", 0)
- g("d_inventory", 0)
+ g("d_ap", 0)
+ g("d_accrued", 0)
)
cfi = -g("capex", 0)
cff = g("d_debt", 0) + g("d_stock", 0)
d_cash = cfo + cfi + cff
# -- Balance sheet -----------------------------------------------------
d_ppe = g("capex", 0) - g("depreciation", 0)
d_inventory_bs = g("d_inventory", 0) - g("writedown", 0)
d_assets = d_cash + g("d_ar", 0) + d_inventory_bs + d_ppe
d_liabilities = g("d_ap", 0) + g("d_accrued", 0) + g("d_debt", 0)
d_equity = g("d_stock", 0) + net_income # retained earnings; no dividends
return {
"net_income": net_income, "cfo": cfo, "cfi": cfi, "cff": cff,
"d_cash": d_cash, "d_assets": d_assets,
"d_liabilities": d_liabilities, "d_equity": d_equity,
"gap": d_assets - (d_liabilities + d_equity),
}
SCENARIOS = [
("Depreciation +10", {"depreciation": 10}),
("Write down $10 of inventory", {"writedown": 10}),
("Accrue $10 of expense, unpaid", {"cash_opex": 10, "d_accrued": 10}),
("Buy $10 of inventory for cash", {"d_inventory": 10}),
("Spend $10 on capex", {"capex": 10}),
("Issue $100 of debt at 5%", {"d_debt": 100, "interest": 5}),
("Buy back $50 of stock", {"d_stock": -50}),
]
def n(x):
return f"{round(x * 100) / 100:.2f}".rjust(8)
print("tax rate " + f"{TAX * 100:.0f}" + "% (every number below follows from it)")
print("")
print("scenario net inc CFO CFI CFF cash assets L + E balances")
for name, s in SCENARIOS:
r = statements(s)
print(
" " + name.ljust(32),
n(r["net_income"]), n(r["cfo"]), n(r["cfi"]), n(r["cff"]), n(r["d_cash"]),
n(r["d_assets"]), n(r["d_liabilities"] + r["d_equity"]),
" yes" if abs(r["gap"]) < 1e-9 else " NO (" + f"{r['gap']:.2f}" + ")",
)
broken = [s for _, s in SCENARIOS if abs(statements(s)["gap"]) > 1e-9]
print("")
print(
"All " + str(len(SCENARIOS)) + " balance. The identity is not a checksum you apply at"
if len(broken) == 0
else str(len(broken)) + " DO NOT BALANCE -- the scenario is mis-specified."
)
print("the end; it is the thing that makes the third number derivable from")
print("the first two.")
print("")
# -- The two rows that look identical and are not --------------------------
dep = statements({"depreciation": 10})
inv = statements({"d_inventory": 10})
def sgn(x):
return ("+" if x >= 0 else "") + f"{x:.2f}"
print("Rows 1 and 4 are the pair worth staring at.")
print(" Depreciation +10: income " + sgn(dep["net_income"]).rjust(6) +
" cash " + sgn(dep["d_cash"]).rjust(6) +
" assets " + sgn(dep["d_assets"]).rjust(6))
print(" Inventory +10: income " + sgn(inv["net_income"]).rjust(6) +
" cash " + sgn(inv["d_cash"]).rjust(6) +
" assets " + sgn(inv["d_assets"]).rjust(6))
print("One is an expense that moves no cash. The other is cash that is not")
print("an expense. Candidates who have memorised answers rather than the")
print("system reliably swap them.")
print("")
# -- The answer is a function of the tax rate, not a number ----------------
print("Same $10 of depreciation, four tax rates:")
print(" tax net income cash assets RE check: -10*(1-t) and +10*t")
for t in [0, 0.21, 0.25, 0.40]:
r = statements({"depreciation": 10}, t)
ok_ni = abs(r["net_income"] - -10 * (1 - t)) < 1e-9
ok_cash = abs(r["d_cash"] - 10 * t) < 1e-9
print(
" " + f"{t * 100:.0f}".rjust(3) + "%",
n(r["net_income"]), n(r["d_cash"]), n(r["d_assets"]), n(r["d_equity"]),
" " + ("both hold" if ok_ni and ok_cash else "FAILED"),
)
print("")
print("So the whole of the classic answer is two expressions:")
print(" net income moves by -expense x (1 - t)")
print(" cash moves by +expense x t <- the tax shield")
print("At t = 0 the tax shield is zero and cash does not move at all, which")
print("is the fastest way to prove to yourself that the cash increase was")
print("never about the expense. It was always about the deduction.")
tax rate 25% (every number below follows from it)
scenario net inc CFO CFI CFF cash assets L + E balances
Depreciation +10 -7.50 2.50 0.00 0.00 2.50 -7.50 -7.50 yes
Write down $10 of inventory -7.50 2.50 0.00 0.00 2.50 -7.50 -7.50 yes
Accrue $10 of expense, unpaid -7.50 2.50 0.00 0.00 2.50 2.50 2.50 yes
Buy $10 of inventory for cash 0.00 -10.00 0.00 0.00 -10.00 0.00 0.00 yes
Spend $10 on capex 0.00 0.00 -10.00 0.00 -10.00 0.00 0.00 yes
Issue $100 of debt at 5% -3.75 -3.75 0.00 100.00 96.25 96.25 96.25 yes
Buy back $50 of stock 0.00 0.00 0.00 -50.00 -50.00 -50.00 -50.00 yes
All 7 balance. The identity is not a checksum you apply at
the end; it is the thing that makes the third number derivable from
the first two.
Rows 1 and 4 are the pair worth staring at.
Depreciation +10: income -7.50 cash +2.50 assets -7.50
Inventory +10: income +0.00 cash -10.00 assets +0.00
One is an expense that moves no cash. The other is cash that is not
an expense. Candidates who have memorised answers rather than the
system reliably swap them.
Same $10 of depreciation, four tax rates:
tax net income cash assets RE check: -10*(1-t) and +10*t
0% -10.00 0.00 -10.00 -10.00 both hold
21% -7.90 2.10 -7.90 -7.90 both hold
25% -7.50 2.50 -7.50 -7.50 both hold
40% -6.00 4.00 -6.00 -6.00 both hold
So the whole of the classic answer is two expressions:
net income moves by -expense x (1 - t)
cash moves by +expense x t <- the tax shield
At t = 0 the tax shield is zero and cash does not move at all, which
is the fastest way to prove to yourself that the cash increase was
never about the expense. It was always about the deduction.
The two tabs differ in three places worth knowing about, and all three are the same kind of thing — the language's way of expressing an intent the other one has to spell out. s[k] || 0 in JavaScript becomes s.get(k, 0) in Python, which is more honest because it says "default", not "falsy"; the JavaScript version would also silently convert null or "" to 0, harmlessly here but not always. The JavaScript object returned by statements reads as r.netIncome; the Python dict reads as r["net_income"], and the key names change case with it because that is each language's convention. And the padding helpers swap padStart/padEnd for rjust/ljust. Nothing about the accounting changes, which is why both tabs print the same bytes.
What the output actually established
The canonical answer fell out; it was not typed in. At 25% the model produces net income −$7.50, cash +$2.50, PP&E −$10 and retained earnings −$7.50 — which matches the published walkthroughs exactly, and matches the 20%-tax version of the same walkthrough when you change one constant. The two independent sources agreeing with a model neither of them wrote is the check that the model is right.
Three moves, all in dollars. The expense takes $10 off pre-tax income, the deduction hands $2.50 of it back, and then the add-back returns the whole $10 because no cash ever left. The middle bar is the entire answer — remove it and the walk ends at zero, which is exactly what the 0% row of the tax sweep shows. Cash rose because of the deduction, not because of the expense.
The answer is two expressions. Net income moves by -expense × (1 - t) and cash moves by +expense × t. Now the question is rate-independent: at 21% it is −$7.90 and +$2.10, and you did not have to remember that.
The tax-rate sweep contains the explanation. At a tax rate of zero, cash does not move at all. That single row is the fastest available proof that the cash increase was never about the expense — a non-cash expense cannot move cash, and it doesn't. It was always about the deduction. Saying that sentence out loud is what separates an answer from a recitation, because it explains the mechanism instead of reporting the arithmetic.
The pair that gets swapped
Rows 1 and 4 of the output look almost the same and are opposites:
- Depreciation +$10 — income −$7.50, cash +$2.50. An expense that moves no cash.
- Inventory +$10 bought for cash — income $0.00, cash −$10.00. Cash that is not an expense.
Buying inventory does not touch the income statement at all. Nothing was consumed; one asset became another. It hits the income statement later, as cost of goods sold, when the inventory is sold. The write-down row is the bridge between the two: writing inventory down is an expense, and it behaves exactly like depreciation — income −$7.50, cash +$2.50, and the asset gone from the balance sheet.
This is the confusion the question is designed to find, and it is why "walk me through it" is the phrasing rather than "what is the answer".
The assumption in the header, and the follow-up it invites
The model books tax and pays tax in the same period at the same rate. Real companies do not, and the gap is the single most common follow-up to a depreciation question.
Tax authorities and accounting rules use different depreciation schedules. A company commonly depreciates faster for tax purposes than for its books, which means the tax it pays in early years is lower than the tax expense it reports. That difference does not vanish — it reverses later, when the asset is fully depreciated for tax and still being depreciated for the books. In the meantime the company owes tax it has not yet paid, and that obligation sits on the balance sheet as a deferred tax liability.
Practising this so it holds
The reason to have run the cell rather than read a walkthrough is that you can now change one thing and see the whole system respond. Three modifications worth making before you close the tab:
- Set
TAX = 0.21and re-run. Every number in the table changes and the two closing expressions still hold. That is the property you want in your head, not the table. - Add a scenario for stock compensation —
{ cashOpex: 0, ... }is the wrong start; stock comp is an expense that moves no cash and increases equity. Getting the identity to balance requires adding adStockleg of the same size. If you cannot make it balance, you have found a real gap in your model of it. - Break one deliberately. Change the accrual scenario to
{ cashOpex: 10 }with nodAccruedand watch the check fail. That is the shape of the error you are trying to avoid making out loud: an expense recorded with no corresponding source of funds.
The DCF in the next lesson is built on this system — free cash flow is net income with the non-cash charges added back and the capital spending taken out, which is the cash flow statement rearranged. If the linkage is solid, the DCF is bookkeeping. If it is not, the DCF is memorisation on top of memorisation.