Lease Accounting with IFRS 16 / ASC 842

BusinessMath Quarterly Series

15 min read

Part 15 of 12-Week BusinessMath Series


What You’ll Learn


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: This affects nearly every business with operating leases (office space, equipment, vehicles). CFOs need to: Manual lease accounting in spreadsheets is error-prone and doesn’t scale when you have dozens or hundreds of leases.

The Solution

BusinessMath provides the Lease 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)))")
→ Full API Reference: BusinessMath Docs – 3.6 Lease Accounting

Modifications to try:

  1. Model your company’s actual office lease
  2. Compare finance lease vs. operating lease treatment
  3. Analyze lease-vs-buy decisions for equipment
  4. Model lease modifications (extensions, rent reductions)

Real-World Application

Every public company with leases must comply with IFRS 16 / ASC 842: Example - Delta Air Lines: Adopted ASC 842 and added $8.5 billion in lease liabilities to the balance sheet. Their debt-to-equity ratio instantly increased from 1.5x to 2.8x.

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:

IFRS 16 / ASC 842 solved this by requiring capitalization of virtually all leases. Now the balance sheet reflects the economic reality: if you have the right to use an asset and an obligation to pay, that’s an asset and liability.

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:

  1. IFRS 16 / ASC 842 explicitly require it
  2. Users who forget will have incorrect financials
  3. Edge cases can override with optional parameters
But this means the API embeds accounting assumptions. If standards change (e.g., IFRS 17 for insurance), the API must evolve.

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:


Tagged with: financial-analysis