Savings vs Credit-Card Debt

Same card, same income - one number decides everything: pay just under the interest and the debt runs away; pay just over it and the balance marches to zero.

Level: Beginner

debtreinforcing-loopexponentialcompounding

  • Stocks: savings_balance, debt_balance
  • Flows: interest, deposit, payment
  • Feedback Loops: compound interest on savings (reinforcing), compound interest on debt (reinforcing)
  • Probes: savings_balance, debt_balance

Dynamic Behavior Patterns

Explore common system behaviors: exponential growth, goal-seeking decay, overshoot-and-collapse, and S-curve saturation.

Take the Quiz
simulation.py

The minimum-payment trap

Savings and credit-card debt both compound every month. Whether the debt shrinks or explodes hinges on one number: the monthly payment. Pay more than the interest and the balance marches to zero; pay less, and the unpaid interest is rolled into the balance, which makes next month's interest larger still — a reinforcing loop that runs away.

The knob is monthly_payment. The tipping point is the monthly interest debt_rate * balance: pay below it and the debt grows without end.


from tys import probe, progress

Simulate monthly savings and debt balances.

def simulate(cfg: dict):

    import simpy
    env = simpy.Environment()

    savings = cfg["initial_savings"]
    debt = cfg["initial_debt"]
    savings_rate = cfg["savings_rate"]      # monthly interest rate on savings
    debt_rate = cfg["debt_rate"]            # monthly interest rate on debt
    deposit = cfg["monthly_deposit"]        # added to savings each month
    payment = cfg["monthly_payment"]        # paid toward debt each month
    months = cfg["months"]
    initial_debt = debt

    done = env.event()

Apply interest, then deposits and payments, each month.

    def cycle():
        nonlocal savings, debt
        for m in range(months):
            savings = savings * (1 + savings_rate) + deposit
            debt = max(0, debt * (1 + debt_rate) - payment)   # interest first, then the payment
            probe("savings_balance", env.now, savings)
            probe("debt_balance", env.now, debt)
            progress(int(100 * (m + 1) / months))
            yield env.timeout(1)
        done.succeed({
            "final_savings": savings,
            "final_debt": debt,
            "debt_ran_away": debt > initial_debt,
            "debt_paid_off": debt <= 0.01,
        })

    env.process(cycle())
    env.run(until=done)
    return done.value


def requirements():
    return {
        "external": ["simpy==4.1.1"],
    }
Minimum-Payment Trap.yaml
# trap.yaml — the default: monthly_payment sits below the monthly interest
# (~40/mo at the start), so the debt grows without end.
initial_savings: 1000
initial_debt: 2000
savings_rate: 0.005
debt_rate: 0.02
monthly_deposit: 100
monthly_payment: 25
months: 48
Charts (Minimum-Payment Trap)

savings_balance

Samples48 @ 0.00–47.00
Valuesmin 1105.00, mean 3786.14, median 3729.53, max 6680.27, σ 1642.70

debt_balance

Samples48 @ 0.00–47.00
Valuesmin 2015.00, mean 2514.70, median 2468.39, max 3190.30, σ 344.38
Final Results (Minimum-Payment Trap)
MetricValue
final_savings6680.27
final_debt3190.30
debt_ran_awaytrue
debt_paid_offfalse
Paying It Down.yaml
# paydown.yaml — the contrast: identical except monthly_payment (25 -> 70),
# which clears the monthly interest and drives the balance to zero.
initial_savings: 1000
initial_debt: 2000
savings_rate: 0.005
debt_rate: 0.02
monthly_deposit: 100
monthly_payment: 70
months: 48
Charts (Paying It Down)

savings_balance

Samples48 @ 0.00–47.00
Valuesmin 1105.00, mean 3786.14, median 3729.53, max 6680.27, σ 1642.70

debt_balance

Samples48 @ 0.00–47.00
Valuesmin 0.00, mean 995.02, median 1063.22, max 1970.00, σ 647.83
Final Results (Paying It Down)
MetricValue
final_savings6680.27
final_debt0.00
debt_ran_awayfalse
debt_paid_offtrue
FAQ
When does the debt start to run away?
When the monthly payment is less than the interest charged that month (debt_rate * balance). The unpaid interest is added to the balance, so next month's interest is larger still - the minimum-payment trap.
Both runs use the same card. Why does one debt vanish and the other grow?
Only monthly_payment differs. At 25 it sits below the ~40/mo starting interest, so the balance climbs; at 70 it clears the interest and chips away at the principal until the balance reaches zero.
Why does the savings line look the same in both runs?
The savings side (deposit and interest) is held fixed, so the only thing that changes is the debt payment - savings is the steady reference the debt is measured against.
What is the real-world lesson?
Paying only the minimum on a high-interest card can leave you owing more over time, even while you pay every single month. Beating the monthly interest is what turns the balance around.