Lease Accounting with IFRS 16 / ASC 842

BusinessMath Quarterly Series

13 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

This affects nearly every business with operating leases (office space, equipment, vehicles). CFOs need to:

  • 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

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:

  • Retailers: Store leases (hundreds or thousands)
  • Airlines: Aircraft leases (multi-billion dollar liabilities)
  • Tech companies: Office space, data centers
  • Manufacturing: Equipment leases

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:

  • 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

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:

  • Week: 4/12
  • Posts Published: 15/~48
  • Topics Covered: Foundation + Analysis + Operational + Financial Statements (complete)
  • Playgrounds: 14 available


Tagged with: businessmath, swift, leases, ifrs16, asc842, accounting, right-of-use-assets