Skip to content

Compound Interest Calculator — Kotlin source

Project investment growth with any compounding frequency, scheduled contributions and inflation adjustment. Yearly breakdown, growth curve and CSV export — runs entirely in your browser.

This is the Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.

// compound-interest-calculator — compound growth projection.
//
// Language: Kotlin (1.9, standard library only)
// Source:   CosmoDev polyglot showcase port of the Compound Interest
//           Calculator tool, ported from src/lib/interest.ts (the canonical
//           TypeScript implementation).
// Tool:     https://dev.cosmolabs.org/tools/compound-interest-calculator
// License:  display source — part of CosmoDev's polyglot tool pages.
//
// Display snippet: ports the core project() math from the TypeScript lib.
// The full projection (contributions, inflation, per-year rows) lives in
// the TypeScript/Go sources.
//
// Semantics: monthly simulation with the monthly rate set to the exact
// equivalent of the stated compounding frequency — (1 + r/n)^(n/12) - 1 —
// so the effective annual rate is preserved for every supported frequency.
// Returns the final balance rounded to 2 decimals, or null on invalid
// input (negative principal, negative years, unsupported frequency,
// non-finite values).

import kotlin.math.floor
import kotlin.math.pow

object CompoundInterestCalculator {

    private val VALID_COMPOUNDS = setOf(1, 2, 4, 12, 365)

    /** Round to 2 decimals, half-up (mirrors the TS Math.round). */
    fun round2(n: Double): Double = floor(n * 100.0 + 0.5) / 100.0

    fun project(principal: Double, ratePct: Double, years: Int, compoundsPerYear: Int): Double? {
        if (!principal.isFinite() || !ratePct.isFinite()) return null
        if (principal < 0.0 || years < 0 || compoundsPerYear !in VALID_COMPOUNDS) return null

        val n = compoundsPerYear.toDouble()
        val monthlyRate = (1.0 + ratePct / 100.0 / n).pow(n / 12.0) - 1.0

        var balance = principal
        repeat(years * 12) { balance *= 1.0 + monthlyRate }
        return round2(balance)
    }
}

fun main() {
    // Example:
    //   project(10000, 5, 10, 12) -> 16470.09
    val result = CompoundInterestCalculator.project(10000.0, 5.0, 10, 12)
    check(result == 16470.09) { "expected 16470.09, got $result" }

    // Invalid inputs are rejected, never clamped.
    check(CompoundInterestCalculator.project(-1.0, 5.0, 10, 12) == null) { "negative principal" }
    check(CompoundInterestCalculator.project(10000.0, 5.0, -1, 12) == null) { "negative years" }
    check(CompoundInterestCalculator.project(10000.0, 5.0, 10, 6) == null) { "unsupported frequency" }
    check(CompoundInterestCalculator.project(Double.POSITIVE_INFINITY, 5.0, 10, 12) == null) { "non-finite value" }

    println("ok")
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →