Lease Accounting with IFRS 16 / ASC 842
BusinessMath Quarterly Series
15 min read
Part 15 of 12-Week BusinessMath Series
What You’ll Learn
- Calculating lease liabilities as present value of future payments
- Modeling right-of-use (ROU) assets with initial direct costs
- Generating amortization schedules with interest and principal breakdown
- Computing depreciation expense for ROU assets
- Applying short-term and low-value lease exemptions
- Understanding discount rate selection (implicit rate vs. IBR)
The Problem
In 2019, new lease accounting standards (IFRS 16 and ASC 842) fundamentally changed how companies report leases. Most leases must now be capitalized on the balance sheet, creating:- Lease Liability: Present value of future lease payments
- Right-of-Use Asset: Asset representing the right to use the leased property
- Calculate present value of multi-year payment streams
- Track liability amortization (interest + principal)
- Depreciate ROU assets over the lease term
- Determine which leases qualify for exemptions
- Generate disclosure schedules for auditors
The Solution
BusinessMath provides theLease type with comprehensive tools for lease liability calculation, ROU asset modeling, amortization schedules, and expense tracking.
Basic Lease Recognition
Calculate the initial lease liability and ROU asset:import BusinessMath
// Office lease: quarterly payments for 1 year
let q1 = Period.quarter(year: 2025, quarter: 1)
let periods = [q1, q1 + 1, q1 + 2, q1 + 3]
let payments = TimeSeries(
periods: periods,
values: [25_000.0, 25_000.0, 25_000.0, 25_000.0]
)
// Create lease with 6% annual discount rate (incremental borrowing rate)
let lease = Lease(
payments: payments,
discountRate: 0.06
)
// Calculate present value (lease liability)
let liability = lease.presentValue()
print(“Initial lease liability: (liability.currency())”) // ~$96,360
// Calculate right-of-use asset (initially equals liability)
let rouAsset = lease.rightOfUseAsset()
print(“ROU asset: (rouAsset.currency())”) // $96,360
Output:
Initial lease liability: $96,360
ROU asset: $96,360
The calculation: Four $25,000 payments discounted at 6% annual (1.5% quarterly) = $96,360 present value.
Lease Liability Amortization Schedule
Generate a complete amortization schedule showing how the liability decreases each period:let schedule = lease.liabilitySchedule()
print(”=== Lease Liability Schedule ===”)
print(“Period\t\tBeginning\tPayment\t\tInterest\tPrincipal\tEnding”)
print(”——\t\t———\t—––\t\t––––\t———\t——”)
for (i, period) in periods.enumerated() {
// Beginning balance
let beginning = i == 0 ? liability : schedule[periods[i-1]]!
// Payment
let payment = payments[period]!
// Interest expense (Beginning × quarterly rate)
let interest = lease.interestExpense(period: period)
// Principal reduction
let principal = lease.principalReduction(period: period)
// Ending balance
let ending = schedule[period]!
print(”(period.label)(beginning.currency(0).paddingLeft(toLength: 14))(payment.currency(0).paddingLeft(toLength: 10))(interest.currency(0).paddingLeft(toLength: 13))(principal.currency(0).paddingLeft(toLength: 13))(ending.currency(0).paddingLeft(toLength: 9))”)
}
print(”\nTotal payments: ((payments.reduce(0, +)).currency(0))”)
print(“Total interest: ((lease.totalInterest()).currency(0))”)
Output:
=== Lease Liability Schedule ===
Period Beginning Payment Interest Principal Ending
—— ——— —–– –––– ——— ——
2025-Q1 $96,360 $25,000 $1,445 $23,555 $96,360
2025-Q2 $96,360 $25,000 $1,092 $23,908 $48,897
2025-Q3 $48,897 $25,000 $733 $24,267 $24,631
2025-Q4 $24,631 $25,000 $369 $24,631 $0
Total payments: $100,000
Total interest: $3,640
The insight: Interest expense decreases each period as the liability balance declines (front-loaded interest).
Including Initial Direct Costs and Prepayments
Many leases include upfront costs that increase the ROU asset:let leaseWithCosts = Lease(
payments: payments,
discountRate: 0.06,
initialDirectCosts: 5_000.0, // Legal fees, broker commissions
prepaidAmount: 10_000.0 // First month rent + security deposit
)
let liability = leaseWithCosts.presentValue() // PV of payments only
let rouAsset = leaseWithCosts.rightOfUseAsset() // PV + costs + prepayments
print(”=== Initial Recognition with Costs ===”)
print(“Lease liability: (liability.currency())”) // $96,454
print(“ROU asset: (rouAsset.currency())”) // $111,454
print(”\nDifference: ((rouAsset - liability).currency())”) // $15,000 (costs + prepayment)
Output:
=== Initial Recognition with Costs ===
Lease liability: $96,360
ROU asset: $111,360
Difference: $15,000
The accounting: Liability = PV of future payments. Asset = Liability + upfront costs + prepayments.
Depreciation of ROU Asset
ROU assets are depreciated straight-line over the lease term:print(”\n=== ROU Asset Depreciation ===”)
// Quarterly depreciation (straight-line over 4 quarters)
let depreciation = leaseWithCosts.depreciation(period: q1)
print(“Quarterly depreciation: (depreciation.currency())”) // $111,454 ÷ 4 = $27,864
// Track carrying value each quarter
for (i, period) in periods.enumerated() {
let carryingValue = leaseWithCosts.carryingValue(period: period)
let quarterNum = i + 1
print(“Q(quarterNum) carrying value: (carryingValue.currency())”)
}
Output:
=== ROU Asset Depreciation ===
Quarterly depreciation: $27,840
Q1 carrying value: $83,520
Q2 carrying value: $55,680
Q3 carrying value: $27,840
Q4 carrying value: $0
The pattern: ROU asset decreases linearly by $27,864 each quarter until fully depreciated.
Complete Income Statement Impact
Each period has two expenses: interest and depreciation:print(”\n=== Total P&L Impact by Quarter ===”)
print(“Quarter\tInterest\tDepreciation\tTotal Expense”)
print(”—––\t––––\t————\t———––”)
var totalInterest = 0.0
var totalDepreciation = 0.0
for (i, period) in periods.enumerated() {
let interest = leaseWithCosts.interestExpense(period: period)
let depreciation = leaseWithCosts.depreciation(period: period)
let total = interest + depreciation
totalInterest += interest
totalDepreciation += depreciation
let quarterNum = i + 1
print(“Q(quarterNum)\t(interest.currency())\t(depreciation.currency())\t(total.currency())”)
}
print(”\nTotal:\t(totalInterest.currency())\t(totalDepreciation.currency())\t((totalInterest + totalDepreciation).currency())”)
print(”\n** Note: Expense is front-loaded due to higher interest in early periods”)
Output:
=== Total P&L Impact by Quarter ===
Quarter Interest Depreciation Total Expense
—–– –––– ———— ———––
2025-Q1 $1,445 $27,840 $29,285
2025-Q2 $1,092 $27,840 $28,932
2025-Q3 $733 $27,840 $28,573
2025-Q4 $369 $27,840 $28,209
Total: $3,640 $111,360 $115,000
** Note: Expense is front-loaded due to higher interest in early periods
The insight: Total expense ($115k) exceeds cash payments ($100k) because we’re expensing the upfront costs ($15k) over the lease term.
Short-Term Lease Exemption
Leases of 12 months or less can be expensed instead of capitalized:let shortTermLease = Lease(
payments: payments, // 4 quarterly payments = 12 months
discountRate: 0.06,
leaseTerm: .months(12)
)
if shortTermLease.isShortTerm {
print(”\n✓ Qualifies for short-term exemption”)
print(“Can expense payments as incurred without capitalizing”)
// No balance sheet impact
let rouAsset = shortTermLease.rightOfUseAsset() // Returns 0
print(“ROU asset: (rouAsset.currency())”)
} else {
print(“Must capitalize lease”)
}
Output:
✓ Qualifies for short-term exemption
Can expense payments as incurred without capitalizing
ROU asset: $0.00
The rule: Leases ≤ 12 months can be treated as operating expenses (no capitalization required).
Low-Value Lease Exemption
Leases of assets valued under $5,000 can also be expensed:// Small equipment lease
let lowValueLease = Lease(
payments: payments,
discountRate: 0.06,
underlyingAssetValue: 4_500.0 // Below $5K threshold
)
if lowValueLease.isLowValue {
print(”\n✓ Qualifies for low-value exemption”)
print(“Underlying asset value: (lowValueLease.underlyingAssetValue!.currency())”)
print(“Can expense payments as incurred”)
}
Output:
✓ Qualifies for low-value exemption
Underlying asset value: $4,500.00
Can expense payments as incurred
The rule: Assets with fair value < $5,000 when new (e.g., laptops, small office equipment) can be expensed.
Discount Rate Selection
The discount rate significantly impacts lease valuation:print(”\n=== Impact of Discount Rate ===”)
// Conservative rate (lower discount = higher PV)
let lowRate = Lease(payments: payments, discountRate: 0.04)
// Market rate
let marketRate = Lease(payments: payments, discountRate: 0.06)
// Riskier rate (higher discount = lower PV)
let highRate = Lease(payments: payments, discountRate: 0.10)
print(“At 4% rate: (lowRate.presentValue().currency())”)
print(“At 6% rate: (marketRate.presentValue().currency())”)
print(“At 10% rate: (highRate.presentValue().currency())”)
let difference = lowRate.presentValue() - highRate.presentValue()
print(”\nDifference between 4% and 10%: (difference.currency())”)
Output:
=== Impact of Discount Rate ===
At 4% rate: $97,549.14
At 6% rate: $96,359.62
At 10% rate: $94,049.36
Difference between 4% and 10%: $3,499.78
The insight: Higher discount rates reduce the present value (and thus the balance sheet liability). Companies often use their incremental borrowing rate (IBR).
Multi-Year Lease with Escalations
Real-world leases often have annual rent increases:// 5-year office lease with 3% annual escalation
let startDate = Period.quarter(year: 2025, quarter: 1)
let fiveYearPeriods = (0..<20).map { startDate + $0 } // 20 quarters
// Generate escalating payments
var escalatingPayments: [Double] = []
let baseRent = 30_000.0
for i in 0..<20 {
let yearIndex = i / 4 // Which year (0-4)
let escalatedRent = baseRent * pow(1.03, Double(yearIndex))
escalatingPayments.append(escalatedRent)
}
let paymentSeries = TimeSeries(periods: fiveYearPeriods, values: escalatingPayments)
let longTermLease = Lease(
payments: paymentSeries,
discountRate: 0.068, // 6.8% IBR
initialDirectCosts: 15_000.0,
prepaidAmount: 30_000.0
)
let liability = longTermLease.presentValue()
let rouAsset = longTermLease.rightOfUseAsset()
print(”\n=== 5-Year Office Lease ===”)
print(“Base quarterly rent: (baseRent.currency())”)
print(“Total payments (nominal): (paymentSeries.reduce(0, +).currency())”)
print(“Present value: (liability.currency())”)
print(“ROU asset: (rouAsset.currency())”)
print(”\nDiscount: ((paymentSeries.reduce(0, +) - liability).currency()) (((1 - liability / paymentSeries.reduce(0, +)).percent(1)))”)
Output:
=== 5-Year Office Lease ===
Base quarterly rent: $30,000.00
Total payments (nominal): $637,096.30
Present value: $534,140.43
ROU asset: $579,140.43
Discount: $102,955.86 (16.2%)
The reality: Over 5 years, the present value is ~24% less than nominal payments due to time value of money.
Try It Yourself
Click to expand full playground code
import BusinessMath
// Office lease: quarterly payments for 1 year
let q1 = Period.quarter(year: 2025, quarter: 1)
let periods = [q1, q1 + 1, q1 + 2, q1 + 3]
let payments = TimeSeries(
periods: periods,
values: [25_000.0, 25_000.0, 25_000.0, 25_000.0]
)
// Create lease with 6% annual discount rate (incremental borrowing rate)
let lease = Lease(
payments: payments,
discountRate: 0.06
)
// Calculate present value (lease liability)
let liability = lease.presentValue()
print("Initial lease liability: \(liability.currency(0))") // ~$96,360
// Calculate right-of-use asset (initially equals liability)
let rouAsset = lease.rightOfUseAsset()
print("ROU asset: \(rouAsset.currency(0))") // $96,360
let schedule = lease.liabilitySchedule()
print("=== Lease Liability Schedule ===")
print("Period\t\tBeginning\tPayment\t\tInterest\tPrincipal\tEnding")
print("------\t\t---------\t-------\t\t--------\t---------\t------")
for (i, period) in periods.enumerated() {
// Beginning balance
let beginning = i == 0 ? liability : schedule[periods[i-1]]!
// Payment
let payment = payments[period]!
// Interest expense (Beginning × quarterly rate)
let interest = lease.interestExpense(period: period)
// Principal reduction
let principal = lease.principalReduction(period: period)
// Ending balance
let ending = schedule[period]!
print("\(period.label)\(beginning.currency(0).paddingLeft(toLength: 14))\(payment.currency(0).paddingLeft(toLength: 10))\(interest.currency(0).paddingLeft(toLength: 13))\(principal.currency(0).paddingLeft(toLength: 13))\(ending.currency(0).paddingLeft(toLength: 9))")
}
print("\nTotal payments: \((payments.reduce(0, +)).currency(0))")
print("Total interest: \((lease.totalInterest()).currency(0))")
let leaseWithCosts = Lease(
payments: payments,
discountRate: 0.06,
initialDirectCosts: 5_000.0, // Legal fees, broker commissions
prepaidAmount: 10_000.0 // First month rent + security deposit
)
let liability_wc = leaseWithCosts.presentValue() // PV of payments only
let rouAsset_wc = leaseWithCosts.rightOfUseAsset() // PV + costs + prepayments
print("=== Initial Recognition with Costs ===")
print("Lease liability: \(liability_wc.currency(0))") // $96,360
print("ROU asset: \(rouAsset_wc.currency(0))") // $111,360
print("\nDifference: \((rouAsset_wc - liability_wc).currency(0))") // $15,000 (costs + prepayment)
print("\n=== ROU Asset Depreciation ===")
// Quarterly depreciation (straight-line over 4 quarters)
let depreciation = leaseWithCosts.depreciation(period: q1)
print("Quarterly depreciation: \(depreciation.currency(0))") // $111,454 ÷ 4 = $27,864
// Track carrying value each quarter
for (i, period) in periods.enumerated() {
let carryingValue = leaseWithCosts.carryingValue(period: period)
let quarterNum = i + 1
print("Q\(quarterNum) carrying value: \(carryingValue.currency(0))")
}
print("\n=== Total P&L Impact by Quarter ===")
print("Quarter\tInterest\tDepreciation\tTotal Expense")
print("-------\t--------\t------------\t-------------")
var totalInterest = 0.0
var totalDepreciation = 0.0
for (i, period) in periods.enumerated() {
let interest = leaseWithCosts.interestExpense(period: period)
let depreciation = leaseWithCosts.depreciation(period: period)
let total = interest + depreciation
totalInterest += interest
totalDepreciation += depreciation
let quarterNum = i + 1
print("\(period.label)\(interest.currency(0).paddingLeft(toLength: 9))\(depreciation.currency(0).paddingLeft(toLength: 16))\(total.currency(0).paddingLeft(toLength: 17))")
}
print("\n Total:\(totalInterest.currency(0).paddingLeft(toLength: 9))\(totalDepreciation.currency(0).paddingLeft(toLength: 16))\((totalInterest + totalDepreciation).currency(0).paddingLeft(toLength: 17))")
print("\n** Note: Expense is front-loaded due to higher interest in early periods")
let shortTermLease = Lease(
payments: payments, // 4 quarterly payments = 12 months
discountRate: 0.06,
leaseTerm: .months(12)
)
if shortTermLease.isShortTerm {
print("\n✓ Qualifies for short-term exemption")
print("Can expense payments as incurred without capitalizing")
// No balance sheet impact
let rouAsset = shortTermLease.rightOfUseAsset() // Returns 0
print("ROU asset: \(rouAsset.currency())")
} else {
print("Must capitalize lease")
}
// Small equipment lease
let lowValueLease = Lease(
payments: payments,
discountRate: 0.06,
underlyingAssetValue: 4_500.0 // Below $5K threshold
)
if lowValueLease.isLowValue {
print("\n✓ Qualifies for low-value exemption")
print("Underlying asset value: \(lowValueLease.underlyingAssetValue!.currency())")
print("Can expense payments as incurred")
}
print("\n=== Impact of Discount Rate ===")
// Conservative rate (lower discount = higher PV)
let lowRate = Lease(payments: payments, discountRate: 0.04)
// Market rate
let marketRate = Lease(payments: payments, discountRate: 0.06)
// Riskier rate (higher discount = lower PV)
let highRate = Lease(payments: payments, discountRate: 0.10)
print("At 4% rate: \(lowRate.presentValue().currency())")
print("At 6% rate: \(marketRate.presentValue().currency())")
print("At 10% rate: \(highRate.presentValue().currency())")
let difference = lowRate.presentValue() - highRate.presentValue()
print("\nDifference between 4% and 10%: \(difference.currency())")
// 5-year office lease with 3% annual escalation
let startDate = Period.quarter(year: 2025, quarter: 1)
let fiveYearPeriods = (0..<20).map { startDate + $0 } // 20 quarters
// Generate escalating payments
var escalatingPayments: [Double] = []
let baseRent = 30_000.0
for i in 0..<20 {
let yearIndex = i / 4 // Which year (0-4)
let escalatedRent = baseRent * pow(1.03, Double(yearIndex))
escalatingPayments.append(escalatedRent)
}
let paymentSeries = TimeSeries(periods: fiveYearPeriods, values: escalatingPayments)
let longTermLease = Lease(
payments: paymentSeries,
discountRate: 0.068, // 6.8% IBR
initialDirectCosts: 15_000.0,
prepaidAmount: 30_000.0
)
let liability_ep = longTermLease.presentValue()
let rouAsset_ep = longTermLease.rightOfUseAsset()
print("\n=== 5-Year Office Lease ===")
print("Base quarterly rent: \(baseRent.currency())")
print("Total payments (nominal): \(paymentSeries.reduce(0, +).currency())")
print("Present value: \(liability_ep.currency())")
print("ROU asset: \(rouAsset_ep.currency())")
print("\nDiscount: \((paymentSeries.reduce(0, +) - liability_ep).currency()) (\((1 - liability_ep / paymentSeries.reduce(0, +)).percent(1)))")
Modifications to try:
- Model your company’s actual office lease
- Compare finance lease vs. operating lease treatment
- Analyze lease-vs-buy decisions for equipment
- Model lease modifications (extensions, rent reductions)
Real-World Application
Every public company with leases must comply with IFRS 16 / ASC 842:- Retailers: Store leases (hundreds or thousands)
- Airlines: Aircraft leases (multi-billion dollar liabilities)
- Tech companies: Office space, data centers
- Manufacturing: Equipment leases
CFO use case: “We have 250 office leases across 30 countries. I need to calculate the total lease liability and ROU asset for our quarterly 10-Q filing, broken down by currency and region.”
BusinessMath makes this programmatic, auditable, and reproducible.
★ Insight ─────────────────────────────────────
Why the New Lease Accounting Standards?
Under old rules (IAS 17 / FAS 13), operating leases were off-balance-sheet.
This meant:
- Hidden leverage: Airlines had billions in lease obligations not on the balance sheet
- Comparability issues: Two identical companies with different lease-vs-buy decisions looked completely different financially
- Analyst adjustments: Every analyst had to manually capitalize operating leases to compare companies
Trade-off: More complexity, but greater transparency.
─────────────────────────────────────────────────
📝 Development Note
The hardest design decision for lease accounting was: How much to embed accounting rules in the API vs. leaving flexibility?Example dilemma: Should Lease.rightOfUseAsset() automatically include initial direct costs? Or require the user to add them separately?
We chose automatic inclusion because:
- IFRS 16 / ASC 842 explicitly require it
- Users who forget will have incorrect financials
- Edge cases can override with optional parameters
The lesson: For domain-specific APIs (accounting, tax, legal), embedding rules improves correctness but reduces flexibility. Choose based on your users’ expertise—CPAs benefit from enforced rules; accountants building custom models need flexibility.
Related Methodology: Test-First Development (Week 1) - We wrote failing tests for IFRS 16 requirements first, ensuring compliance.
Next Steps
Coming up next week: Week 5 explores loans, investments, and valuation techniques.Monday: Loan Amortization - Building schedules for mortgages, car loans, and term loans.
Series Progress:
- Week: 4/12
- Posts Published: 15/~48
- Topics Covered: Foundation + Analysis + Operational + Financial Statements (complete)
- Playgrounds: 14 available
Tagged with: financial-analysis