nick gavalas

web performance & musings

personal finance for programmers: backdoor roth ira _

// date: 16 oct 2025 tags: personal finance

The easiest bug in the tax code to explain to programmers has got to be the backdoor Roth IRA. At least, it certainly feels like a bug.

what are roth IRAs and where do they fit in?

This is not meant to be a comprehensive guide1 to Roth IRAs and this is not financial advice.

The tl;dr is that you get to put post-tax money into a retirement account now and then essentially never pay tax on it again. This is an alternative to “traditional” retirement accounts that deduct pre-tax dollars now and make you pay taxes later (hopefully at a lower rate) or regular taxable brokerage accounts you can open at your favorite brokerage with no tax advantage whatsoever. In 2025, you can contribute up to $7000 (or $8000 if you’re over 50).

If that was the whole story, this is a no-brainer; given the choice between a Roth and dropping the money in an index fund in a regular brokerage account, it makes good sense to fill up the Roth every year and avoid taxes on that money forever. This is lower on the priority list than covering your monthly spend, having an emergency fund, or maxing your employer 401k match, but otherwise it’s pretty up there.

Here’s the rub: there’s (theoretically, as you’ll see) an income (MAGI) limit on contributing to this account. In 2025, the contribution phase-out ranges from $150k-165k for single filers and $236k-246k for married filing jointly2; it phases out linearly from the start of the range:

function roth_ira_limit(age: number, income: number): number {
  const upper = 165000;
  const lower = 150000;
  const limit = age >= 50 ? 8000 : 7000; // there's also super catchup for 60-63
  if (income < lower) {
    // can only contribute earned dollars
    return Math.min(income, limit);
  } else if (income > upper) {
    // can't contribute at all
    return 0;
  }

  // linear phaseout after that
  const leftover = income - lower;
  return limit * (1 - (leftover / (upper - lower)));
}

we’re out of luck…or?

Well, hopefully the name “backdoor Roth” gives you an idea of where this is going. The income limit, in a lot of cases, is not really a limit.

In the IRA world, the alternative to a Roth IRA is a “traditional” IRA. This functions a lot like your traditional 401k through your employer: you contribute pre-tax dollars and get to take a deduction now, then pay tax much, much later. This is usually good, but it turns out the deduction has an even lower income limit as long as your employer offers an employer-sponsored retirement account like a 401k.

Does that make the traditional IRA useless to contribute to for high earners? Essentially, yes. There isn’t a great reason to contribute to one of these if you can’t deduct; you might as well just put it in a normal brokerage account and not need to worry about all the rules around RMDs and other retirement account trivia.

However, one man’s non-deductible contribution is another man’s backdoor, or something. It’s these otherwise useless non-deductible traditional IRA contributions that form the basis for the backdoor Roth.

the “backdoor”

The missing detail is that you are allowed to convert money held in a traditional IRA into your Roth IRA, no matter your income. If the money is pre-tax (i.e., you took a deduction when contributing), you have to pay tax on it first; if it’s after-tax, you don’t pay tax on it again when converting (which would be double taxation).

Do you spot the bug with this process?

type RothDollars = number;
type TradIraDollars = [number, number]; // pre-tax dollars, post-tax dollars

function contribute_to_trad_ira(age: number, income: number): TradIraDollars {
  const trad_ira_balances = get_trad_ira_balances();
  if (income > 77000) {
    // you earn too much, so you can't do pre-tax, but post-tax is fine...
    let limit = age >= 50 ? 8000 : 7000;
    return [trad_ira_balances[0], trad_ira_balances[1] + limit];
  }
  // a bunch of boring stuff down here
  ...
}

function contribute_to_roth(age: number, income: number): RothDollars {
  // roth_ira_limit is 0 if you make too much, what a shame
  return rothify(roth_ira_limit(age, income));
}

function trad_to_roth(trad_ira: TradIraDollars): RothDollars {
  // obviously we only want to convert the already-taxed amount, the point of
  // the pre-tax trad IRA is to defer taxes until we make less...
  const after_tax_only = trad_ira[1];

  // no limit on how much you can convert per year / at any income
  return rothify(after_tax_only);
}

function backdoor_roth(age: number, income: number): RothDollars {
  // no income limit here, post-tax trad IRA dollars come out
  const trad_ira_dollars = contribute_to_trad_ira(age, income);

  // since we're allowed to convert at any income...
  // oops, we just got $7k into our Roth IRA this year
  return trad_to_roth(trad_ira_dollars);
}

Essentially, by first making a non-deductible contribution to your traditional IRA, you are allowed to fully convert that into your Roth IRA immediately after, circumventing the income limit entirely.

If you are saving in a taxable brokerage account after funding your other priorities, there’s really no good reason not to do the backdoor contribution to your Roth. You simply get to stash away money that never gets taxed again; you can’t go back in time and contribute for a year that you skipped, so you want to do this every year if possible.

If you have no pre-tax money in your traditional IRA, either from deductible contributions when you made less money (as an intern maybe?) or a rollover IRA from a previous employer, you can stop reading here. Make a traditional IRA and a Roth IRA at the same brokerage (I use Vanguard) and you’ll get a “Convert to Roth IRA button” you can press after you contribute. Super easy3.

the pro-rata rule

Of course, if you do have pre-tax money in any traditional IRA across any brokerage, it’s not so simple. The IRS doesn’t let you “selectively” choose to convert just the after-tax money you contributed as part of the backdoor process and instead requires you to convert proportionally from the pre-tax and post-tax money in your traditional IRA. This is the so-called “pro-rata rule”.

function trad_to_roth_v2(trad_ira: TradIraDollars, roth_convert_amt: number): RothDollars {
  // the IRS said I can't just zero out the pre-tax dollars, apparently?
  let total = trad_ira[0] + trad_ira[1]; // total amounts in all buckets across all trad IRA accounts, globally
  let trad_part = trad_ira[0] / total * roth_convert_amt;
  let roth_part = trad_ira[1] / total * roth_convert_amt;

  // ...which means I get taxed now, which is no fun
  return rothify(tax_me(trad_part) + roth_part);
}

In this case, the backdoor process varies from “a small one-time tax payment” to “completely infeasible”, depending on how much pre-tax money you have. The best thing you can do here, assuming your employer has a good 401k offering, is “reverse rollover” your pre-tax traditional IRA into your employer’s traditional 401k. This leaves your pre-tax traditional IRA at zero so you can continue with the backdoor process tax-free.

conclusion

Legally, I am pretty sure you’re not allowed to tell the IRS, “I think I found a bug in the tax code, so I don’t have to pay taxes now.”

In this case, though, the backdoor Roth process is widely known and completely allowed from a legal standpoint. For high earners, especially if you have no pre-tax traditional IRA contributions, it really is a no-brainer. It’s a shame it’s only $7k per year4 since, in general, the more money you get to turn into Roth dollars the better.


Footnotes

  1. Seriously, this is not comprehensive at all. This is to give you a conceptual understanding of the concepts that make the backdoor Roth possible, not an offering of tax advice. Talk to a CPA if you have concerns about your specific situation.

  2. The story is much worse for married filing separately, where you essentially are not allowed to contribute at all. The backdoor still works for married filing separately, thankfully.

  3. Nothing in your taxes is “Super easy”. You have to pay taxes on any gains you may have on the after-tax money, but if you convert as soon as your initial contribution settles, there should be no gains. This is a slight concern if you wait a few years and convert several years at once, though, but that’s not common.

  4. Or is it…? (spoiler alert for the mega-backdoor Roth)

dark mode toggle