Premium upgrades and downgrades

UPDATE: This is wrong. :frowning: See more recent comment below. Specifically the part about upgrades and downgrades is wrong, though much of the pseudocode may still be useful. It describes a better way to represent premium plans and a useful way to track premium credit if we have other reasons to do that.

This is a bit TMI but I’m sketching out the spec for how to make premium upgrades and downgrades exquisitely fair since we make a big deal about that and I figure I might as well do it it publicly in case y’all want to kibitz or just to have more eyes on it. But for almost everyone it suffices to know that, as we say in the fine print, we just do the opposite of what your phone company would do. The fundamental principle goes like this:

You can jump on any plan with any payment frequency at any time. If you change your mind and upgrade or downgrade you won’t have wasted any money by doing so.

The only way to regret any premium purchase is to buy more than you actually wanted, like buying a year of Bee Plus when you only wanted a month of it. Or to buy Beemium when Bee Plus would’ve sufficed. Or buying lifetime and then dying within 7 years. (Technically you could replace “dying” with, say, solving your akrasia for good or something.) If any of those things happen, talk to us and we’ll make sure you (or your heirs) are happy.

(I think this part’s obvious but to spell it out again: By opting to pay for longer periods, like yearly or lifetime, you’re committing to having that plan (or higher) for that long. That’s the point of the slider – a discount in exchange for committing to a longer period. You can ask for a refund if the perks feel disappointing and we’ll say yes.)

But, mismatched expectations or poor planning aside, the point is that we want everything to be exquisitely fair automatically. With no need to talk to pesky humans. Or even nice humans like us.

For example, if you get yearly Infinibee and then downgrade to core Beeminder, you can do that immediately but you’ll still have Infinibee for a year until what you paid for runs out. Or if you get yearly Bee Plus and then immediately upgrade to monthly Beemium, that’s fine too. You’ll just pay $16 less each month for Beemium because that’s the nominal monthly value of the Bee Plus you already paid for (even though you paid less by buying a year of it). After that year runs out you’ll automatically start paying the normal price for the plan you’re on. You’ll get a credit for the unused portion of your old plan. (If the old plan was lifetime, you’re credited the full amount you paid!) You’ll just draw from that credit when paying for the higher plan until it’s used up.

Corollary: If lifetime Infinibee would be a no-brainer if there weren’t those bigger, shinier plans off to the right, then first grab the lifetime Infinibee. Now any other plan will be $4/mo cheaper for you forever. You can always get the full amount back as a credit to apply to any other plans if you change your mind.

Ok, everyone but the uebernerds can stop reading now.

Algorithms and data structures and pseudocode, oh my!

Ooh, Discourse can mercifully hide gory details till you click here

(The first part of this, the general case for premium subscription credits, now lives at Premium Credits)

There are Plan objects that have the following fields:

  • name is one of: “core”, “infinibee”, (“beelite”), “beeplus”, (“planbee”), “beemium”
  • nom is the nominal monthly amount of the plan ($0 for core thru $32 for beemium)
  • freq is 1 for monthly, 6 for semiannually, 12 for yearly, 24 for biyearly, etc, and 1000 for lifetime
  • cmult is the coupon multiplier, like 0.90 for 10% off (default: 1)
  • paidtil is the timestamp of the next charge (updated as +=freq*SIM when you pay)
  • (chamt is the charge amount each freq months)

The chamt field is in parens because it’s cached and recomputable from the other fields like so:

chamt = nom * cmult * fmult(freq)

where the fmult function is defined like so, taking the payment frequency as a number of months:

fmult(n) := (exp(R) - exp(R - n*R)) / (exp(R) - 1)
fmult(1000) := exp(R) / (exp(R) - 1)  # limit as n goes to infinity

But, to make it easier to special-case that ugly infinity=1000 thing, we actually use this version with an optional second argument:

# Frequency multiplier for n months of a plan, with fraction p remaining
fmult(n, p=1) := (exp(R) - exp(R - n*p*R)) / (exp(R) - 1)
fmult(1000, p=1) := exp(R) / (exp(R) - 1)  # limit as n -> infinity, p irrelevant

For example, if your payment frequency is every 2 months then fmult(2) yields 1.97, slightly less than paying twice as much as the monthly amount every other month. If you were paying every 100 months, fmult(100) yields 32 instead of 100, which is a 68% discount. Note that calling, say, fmult(12, .5) is the same as fmult(6) so that seems dumb to bother with the second argument. We do it so that when we call, say, fmult(1000, .1) we can notice the special case that that’s actually lifetime, not paying every 100 months.

(Yes, we myopically are using “1000” instead of “infinity” for lifetime plans. But don’t worry, we are so on it! That’s 83 years and we have it on our calendar to reimplement Beeminder in a more mathy language that handles infinity nicer by 2096 August 12. Of course we already special-case it so what you pay for a lifetime plan is exactly the NPV of the perpetuity of the nominal amount monthly forever.)


[UPDATE: ignore this section]
Every user has a Plan object, daplan (say it with a Chicago accent). If you downgrade then a second Plan object, downto, is created for you which waits to take effect when daplan runs out. We ignore downto until it’s time to charge for daplan again, at time daplan.paidtil. Then we notice that there’s a downto plan and we replace daplan with downto and start charging for it immediately.


When an upgrade from daplan to upto happens you just add the value of the remainder of daplan to the user’s credit before replacing daplan with upto. First define a function to compute the remaining value in a plan at time t:

valat(p, t) := p.nom * p.cmult * fmult(p.freq, (p.paidtil-t)/(p.freq*SIM))

And then effect the upgrade:

t = now()
addcred(u, valat(daplan, t), t)
daplan = upto

Whenever it’s time to actually charge the user, including immediately after doing the above for the upgrade, we use this:

(Moved to Premium Credits)

And call it like this:

premiumcharge(u, daplan.chamt)

Philosophical and Implementation Notes

  1. (Moved to Premium Credits)

  2. The UI shouldn’t let you downgrade from a lifetime plan. For non-lifetime, the plan being downgraded to should say “Downgrading to this in __ days. [CANCEL]” and the current plan should indicate it’s the current plan like usual. Everything but the cancel button should be grayed out. You have to cancel the downgrade if you want to downgrade or upgrade to anything else.

  3. Actually, I think we can simplify the implementation and the consistency and the user learningness by having no special case for lifetime except to say “infinity days” instead of the live countdown. So you can downgrade but it will end up being a no-op since it will never take effect and the user will understand that because of the “in infinity days” and because there’s no other allowed action than to cancel that. Elegant! And I think that means there’s no lifetime-related cruft in any of the code except for tweaks to some displayed strings in the UI and the limiting case in the fmult function.

  4. Credit balances are only for premium payments, never pledges. No sting-dilution!

  5. Credit balances pay an exorbitantly generous interest rate – the same one that the discount slider uses, 3%/month or 36%/year. Interestingly (tee hee) you could, say, spend $541 for lifetime Bee Plus, upgrade to monthly Beemium and cancel it, leaving yourself a credit of $509 which would earn you $15/month in interest. (Compounding continuously, of course.) Which means you could re-add Bee Plus, paid every 7 months, and basically live off the interest, not touching the principal. Or maybe it doesn’t quite work because you lose a chunk of the principal by paying for 7 months at once. But it’s a pretty sweet deal regardless! But not, I think, so sweet that Beeminder’s leaving too much money on the table. To exploit any of this you have to have lots of faith in us and cough up a lot of money up front. Which is genuinely valuable to us. So it should all still be exquisitely fair.

  6. Fun fact: If you got 7 years of Bee Plus, that would cost $498. If you waited 3.5 years and then upgraded, $388 of that $498 would be left as a credit. Why so much more than half? Because when you pay for 7 years up front, the second 3.5 years are more valuable than the first 3.5 years. Time-value of money. In general you always get back more than the simple arithmetic would suggest. Like if you buy a year and upgrade to something else after a month, you get back as credit slightly more than 11/12 of what you paid.

  7. Translogging: CRE for addcredit, PLA for up/downgrade (including free plan)

  8. Big UI problem mostly orthogonal to all this: reassuring the user that if they have a lifetime or yearly plan that it’s ok to upgrade to a higher plan at a lower frequency. Maybe: “X months free by applying credit from your current plan”. Without credits the idea is to just quote a cheaper price but people’s loss aversion kicks in and they’re still paranoid about losing what they have. With credits they actually do lose it but maybe the trade-in value, if made explicit, will be compelling? This wants some more thought with popup confirmations/explanations so that users can fearlessly be like “sure, I’ll try a bump to Beemium for a month”. What if the upgrade button said something slightly cryptic but intriguing/non-scary like “apply magic upgrade credit” and that popped up a popup that spelled it all out, including how many months free you can get of the newer plan by applying the remaining credit from your current plan?

  9. The X in “X months free” is calculated as follows. First define a function to compute how long you can get a new plan for with a given amount of credit:

# How long can you get a plan w/ nominal monthly cost $m for if you spend $x now?
upfree(m, x) := 
  pow = m / (m*exp(R) - x*exp(R) + x)
  if pow <= 0 then return 1000  # $x is enough for lifetime
  return (R + log(pow)) / R  # show with 2 sigfigs

That’s roughly the inverse of fmult in that fmult gives the cost of buying n months at once while upfree gives the number of months you can get by spending a given amount of money. So now we can compute the X in “X months free”, given a nominal monthly cost m of a new plan and coupon multiplier (from the query string) c like so:

upfree(m*c, valat(daplan, now()))

Scratch area

How I previously spec'd this before deciding it was all wrong

If you upgrade then a new Plan is created and daplan is set to it (upgrades take effect immediately) and your previous Plan is stored as upfrom. It affects the price of your new plan until it runs out.

Seeming monkey wrench that’s not actually that bad: To do this version right, upfrom needs to be a list of plans. When you upgrade you add your current plan to the list. When it’s time to charge for the new current plan (daplan.paidtil == now), you first remove from the list any plans with paidtil in the past (p.paidtil <= daplan.paidtil). Then you pick the maximal p.nom*p.cmult*fmult(daplan.freq) over all the plans p and reduce the payment for daplan, i.e., daplan.chamt, by that amount.

If there were just one upfrom plan then the amount to charge at time daplan.paidtil would be, assuming upfrom.paidtil > daplan.paidtil:

daplan.nom * daplan.cmult * fmult(daplan.freq) - 
upfrom.nom * upfrom.cmult * fmult(daplan.freq)                         [eq1]

(Note that fmult takes daplan.freq in both places. We use the new plan’s frequency to determine how much credit you get from the lower plan you’re upgrading from.)

When upfrom runs out then the user only has daplan and we go back to charging simply nom * cmult * fmult(freq) as stored in daplan.

(It shouldn’t be possible for the amount to charge to be negative, as long as you never “upgrade” to a plan that’s cheaper. But the code should have an assertion and airhorn us just in case.)

How to actually decide what to charge when…

If upfrom and downto are nil then just charge daplan.chamt at time daplan.paidtil.

(Else assert that exactly one of upfrom and downto are nil.)

If upfrom then:

  • charge the amount in [eq1] at time daplan.paidtil
  • daplan.paidtil += daplan.freq*SIM
  • if daplan.paidtil >= upfrom.paidtil then set upfrom = nil

If downto then:

Wait, yikes, this has gotten hairy to do Really Right. Consider the use case of getting yearly Infinibee, then upgrading to semiannual Plan Bee, then upgrading again to monthly Beemium. [There were other, less cornery cases we were getting wrong too but this is the one that convinced me that there was no patching the spaghetti and we had to rethink it.] We either have to convey in the UI that you can’t do that or else store a list of plans you’re upgrading from [not as bad as it seemed – I think this scratch area has the right spec for that now, but @bee says it’s still going to lead to spaghetti] so that while on Beemium you can be credited with the Bee Plus plan you’ve got for the first 6 months and then the Infinibee plan you have for the next 6 months.

I’m hashing this out on paper until I figure it out.

UPDATE: I’m pretty confident now in the credit-based version above!
UPDATE2: No, it was still wrong. The 1000-boxes algorithm is really for realsies right though!




I sent an email on this very subject less than 12 hours ago, but I hope you didn’t write this all up just for me :open_mouth: In any case I’m going to trundle off and buy it.

Looks like the variable has two different names. Similarly for freq and n.

“actually charge is” ?

My gut instinct is that because you adopt the hiplan's frequency there ought to be values of nom and freq which lead to an upgrade path which makes everything beyond the cheapest lifetime plan free. But I’m too lazy / not brazen enough to actually see if the real values Beeminder offers permit such an exploit. :slight_smile:


no, that was coincidence. :slight_smile: but thanks for asking good questions!



PS: I’m treating the above as a wiki and changing all sorts of things about it.

UPDATE: This was all a big conversation with myself. I worked it all out and I’ve incorporated all the conclusions here into the wiki above. So no need to read this post anymore! I’ll use Discourse’s nifty hiding widget to hide it…

Conversation with myself, for you to click on if you're very bored

Update: To do this exquisitely right (which I’m now obsessed with doing) is getting really messy. I’m now taking @bee’s sage advice to design this with an explicit dollar amount that you get as a credit when you upgrade. (Otherwise it got unwieldy to deal with – and convey in the UI – cases like starting with yearly Infinibee, then upgrading to semiannual Bee Plus, then upgrading again to monthly Beemium. You have to store the whole chain to know the perfectly fair amount to charge for the plan you ended up upgrading to.)

So here’s the new plan for upgrades: Say you pay $135 for lifetime Infinibee and use it for 6 months and then decide to upgrade to monthly Bee Plus. That means your lifetime Infinibee disappears but you get a credit equal to the current value of what remains of your Infinibee plan. That’s always $135 no matter how long you’ve had it. Infinity is funny that way.

I think that’s even nicer/fairer – or in any case no less fair – than what I originally described, where you’d get $4/mo off of your new higher plan forever, that being the nominal value of the lower plan you’d already paid for. (See 1st note to self below.)

Now we just need the equation for how much credit you get when you have n months of a plan remaining: nom*fmult(n) or, using the 2-argument version of fmult:

nom * fmult(freq, (paidtil-now) / (freq/12*365.25*86400))

with now and paidtil being unixtimes. Note that if the frequency is lifetime then the credit for upgrading is a constant nom*fmult(1000).

Interestingly, if you got 7 years of Bee Plus, that would cost $498. If you waited 3.5 years and then upgraded, $388 of that $498 would be left as a credit. Why so much more than half? Because when you pay for 7 years up front, the second 3.5 years are more valuable than the first 3.5 years. Time-value of money.

Notes to self:

  1. Here’s a scenario where you could pay slightly more with the credit model than the original model of deducting the nominal amount of the lower plan you’re upgrading from. Buy lifetime Infinibee, then buy a month of Beemium and drop back to lifetime Infinibee. In the original model you’d pay $28 for that month. In the credit model you get a $135 credit when you upgrade which drops to $103 when you get your month of Beemium, and then you spend another $135 – $103 of which comes from your credit, so $32 actually spent – to get your lifetime Infinibee back again. So you’ve effectively paused your lifetime Infinibee to get a month of Beemium at the normal price. Which is pretty reasonable. To be ueberfair we could actually make your credit earn the same 3% per month interest that the discount slider uses. Or maybe a more reasonable interest rate would suffice? If we did go with the exorbitantly generous interest rate then, interestingly, you could, say, spend $541 for lifetime Bee Plus, upgrade to monthly Beemium and cancel it, leaving yourself a credit of $509 which would earn you $15/month in interest. (Compounding continuously, of course.) Which means you could re-add Bee Plus, paid every 7 months, and basically live off the interest, not touching the principal. Or maybe it doesn’t quite work because you lose a chunk of the principal by paying for 7 months at once. But it’s a pretty sweet deal regardless!

  2. For downgrades you have to do the messier thing where you store both the new lower plan and the current higher plan because when you pay for N months of a plan there’s no downgrading before the N months are up. You’ve paid for it and you’re getting it. (The downgrade will happen automatically after N months.) We’ve made a big deal out of this for the trick of subscribing and immediately canceling as a way to buy a single month of higher-plan perks. … Or, maybe this should be revisited? As long as we’re doing credits it’d be pretty nice of us to just let you downgrade immediately and credit you with the prorated amount. So you could spend $4 for a month of Infinibee, quickly create some goals, and cancel it and have a $3.99 credit waiting around for you. That’s a pretty gaping loophole though, where you spend $4 one time and then just dip into it a penny at a time whenever you want more goals. On the other hand it’s the kind of loophole you have to be pretty price-sensitive to bother exploiting so maybe that’s ok?
    Conclusion: No, downgrading happens when the N months you paid for runs out.

  3. Special case for fmult(1000) i.e. fmult(infinity):
    exp(r) / (exp(r) - 1)

  4. Credit should be for premium plans only, not pledges. Especially if we use the exorbitantly generous interest rate.

  5. I think we need to always charge at least a dollar so when applying credit we’ll want to check if the amount to charge minus the credit is less than a dollar, in which case charge $1 and adjust the credit accordingly…

# charge user u $x, first applying as much credit as possible
premiumcharge(u, x) := 
  if >= x -= x  # and don't actually charge anything
  elsif x - credit < 1  # $1 being the least we can charge a credit card
    credit += 1 - x
  else  # wipe out the credit and actually charge the rest
    actually_charge_user(x - credit)
    credit = 0

Language-wise, to be clear, it’s not a credit in the sense of a credit balance. It’s just us taking account of existing unexpired subscription at the point of changing plans.

I think I want to implement it as a literal credit balance, just that it can only be used for future premium payments. You can’t withdraw it or use it to pay pledges.

(Btw, this is all in mass flux at the moment as I work out the right way to do it.)

Implementation detail. Talking about credit balances sounds too much like holding client funds to me.

Also, exquisite fairness to one side, remember that somebody has to implement and maintain the code to run this scheme. If elegant simplicity falls out the other side of this flux, then I’m all for it.

What else could you obsess over that might improve Beeminder more than getting these edge cases perfectly and automatically right?


Speaking of edge cases, I’m one of them, on a lifetime subscription to Plan Bee at the original price-point.

I don’t remember what I paid all that time ago, though I can guess, look up, or calculate it if I cared. It seemed like a good deal at the time, so I went for it.

Likewise, when I go to the premium page now, I get offered a price to change plans. It’s either a price that I like, or a price that I don’t, in relation to my perceived value for the feature difference and my qualitative level of love for the bee.

The actual numbers now vs the numbers then mostly don’t come into it. It’s either going to seem about right (or not) and feel fair (or not).

1 Like

Speaking of Exquisite Fairness, this is a very fair question! :slight_smile: But I think I just figured it out and it’s going to make things simultaneously simpler and better than the status quo. Also I feel honor-bound to make sure we’re following through after my talking up all the Exquisite Fairness. Also I got obsessed / nerd-sniped.

1 Like

How we’re handling this: We’ve already done the refactoring and update queries in the database to store the nominal amount of Plan Bee when you got it, plus any promotional discount you may have used (these were implicit but confusing before). You’ll now (ish) be able to upgrade and get the full original amount you paid as a credit (since you’ve used, technically, 0% so far of the infinite amount of Plan Bee you paid for). On your new fancier plan you’d just not pay anything until that credit was used up.

Grandfatheriness aside, if you were on lifetime Bee Plus and wanted to just try a month of the $32/mo Beemium plan you could do that by upgrading to monthly Beemium and immediately downgrading back to lifetime Bee Plus. That would cost exactly $32 because you’d use up $32 of your upgrade credit and then have to supplement $32 to get lifetime Bee Plus again.

That’s not quite as nice as originally where the lifetime plan is permanent and just reduces the cost of any upgrades. To make up for that, credit balances grow exponentially. As they should, per exquisite fairness.

Hopefully this will all be more intuitive in the UI than it is in prose!

OK, so just to let you all know, the current implementation is not entirely broken or anything.

Here are things that work:

  • lifetime upgrades: if you upgrade to a higher plan after purchasing a lifetime plan at a lower level we reduce the monthly price of the higher plan by the nominal monthly price of the lifetime plan you purchased. So, e.g. with a lifetime infinibee plan, an upgrade to Bee Plus would use $12 (rather than $16) for the nominal monthly price. Then any coupons or slider discounts would be applied to that base price.

  • downgrades and frequency changes: we let the current plan run out, and then make the changes. So if you had purchased a month of Beemium, and then a week later downgraded to Infinibee, we’d wait until that month ran out, and then we’d drop you down to Infinibee and charge you whatever price / frequency you had purchased at.

  • upgrades: Upgrades happen instantaneously. We give you a discount on the first payment by prorating the remainder of the latest payment from your current plan. I.e. if you paid for $4 for 1 month of infinibee and then 3 weeks later upgrade, you’d get (4 * 1/4) = $1 discount on your first payment for the upgrade (which happens right then). Or if you paid $16 for one month of Bee Plus and then immediately upgrade to Beemium, you’d get (16 * 1) = $16 discount of your upgrade price.

Here is what is broken:

These are not perfectly mathematically general and all elegant and stuff, but they are, I believe, sufficiently Fair, except for one bug case, with upgrades. If your prorated discount is greater than your upgrade price, we discount the first payment to $0, but then you lose the remainder of that credit.

Here’s an example of that bug in action: you buy 4 months of BeePlus for $61 / 4 months, and then immediately upgrade to monthly Beemium, i.e. Beemium for $32 / 1 month. Then you’d get the first month for free, but you’d lose that extra $29 of discount credit.

So Danny’s spec fixes this, but the broken case is sufficiently rare at this point in time that there’s a much lazier immediate solution which is to blast off a big airhorn and throw an exception for ourselves when the discount > payment, and then we can manually intercede to make it fair.


Dang, @bee, that was an impressive feat of yanking me out of that rabbit hole by the ears! Seriously, very elegant(ly expedient) stopgap. I’m still super enamored with my spec and want to implement it later [1] but thanks to your wisdom and pragmatism (I’m still being entirely serious!) there’s no longer any urgency to do it Right Now before we inadvertently cheat someone. I mean, relative to the Exquisite Fairness we’ve been crowing about. It’s all so insanely more fair and generous than any other company in existence [2] that it’s pretty funny to be quibbling at all.

But now we’ve got the best of all worlds!

[1] Also it eliminates so much cruft and spaghetti with special cases for lifetime plans and stuff.

[2] Well, I guess companies who are smart enough to not create rabbit holes like this in first place by following KISS from the start can be equally Exquisitely Fair just by not having any messy upgrade/downgrade/discount/frequency corner cases. (That discount slider is sooo sexy though…)


(It just occurred to me that @bee’s solution here is an exquisite example of shirking and turking.)

1 Like

Oh my, @bee and I were chasing a bug today and decided to walk through this spec, mostly for fun since I think it’s super pretty, but maybe just in case it would be easier to actually do this than figure out this latest bug.

Well, instead @bee, in Her wisdom, found a potentially fatal flaw with it. Anguished wail!

I think I now see the Really-For-Real-This-Time Right Way to do this. Very quick sketch while it’s fresh in my mind…

(Oh yeah, first, the potentially fatal flaw is that if you pay for a bunch of, maybe infinite, months of Infinibee up front, then want to pay for just one month of Bee Plus, then go back to Infinibee, it should be impossible to pay more than the Infinibee-to-Bee-Plus delta for that single month. In my spec, we credited you for your unused Infinibee, you use some of that credit to pay full price for Bee Plus, then you have to re-buy your original Infinibee. Of course you earn interest on the credit and on net it’s super generous but if there’s even one scenario where you pay more than what’s exquisitely fair then clearly the whole thing is broken. Bee also pointed out two other problems: a perception-of-fairness problem, which might be overcomable, and a more fundamental problem that when you decide to upgrade, that’s the moment we should charge you, not after some upgrade credit runs out.)

So I pretty much had to concede that all that cleverness with upgrade credits was just wrong. The right thing involves remembering exactly what you paid for for exactly which future months and having no concept of credits or refunds. (I mean, not that kind of “no refunds” – just that refunds happen outside the system, with human intervention.)

Placeholder sketch of how that works:

Internally/conceptually, every user has a 4 by 1000 grid of checkboxes – one for each combination of plan type and month. Starting with this month, extending out to 1000 months (83 years) in the future. Everyone has all the checkboxes for every month checked for the free Core plan. When you add a plan or upgrade at some frequency, that means that starting now, for however long a period you’re paying for, you walk forward month by month checking the unchecked boxes and paying the time-discounted amount for that month for that box.

No boxes can ever by unchecked.

If you downgrade, the UI says “you are downgrading to plan X but still have plan Y until date D” where Y is the highest plan you have at the moment. Maybe that’s Beemium and at date D, because of whatever convoluted chain of upgrades and downgrades you may have done, you then have Bee Plus for some months after. That’s all fine. At that point the UI will change to saying “you are downgrading to plan X but still have Bee Plus until date D”. And maybe “date D” is actually “forever” if you paid for lifetime and that’s fine too. This is items #2 and #3 in the Philosophical and Implementation Notes of my spec above.

But everything is done by walking down that 4x1000 grid of checkboxes that can never be unchecked and that you always pay immediately for checking additional ones when upgrading.

PS: I’m kind of embarrassed that I keep doing this thing where I design an elaborate seemingly pretty thing and then find it’s all fundamentally wrong. I guess I can pat myself on the back for being able to see the wrongness despite having gotten all invested in it. Some would stubbornly dig in ever deeper!


Watch out for the Y2.102K bug!


I only gave your post a glossy-eyed glaze over but I am quite confident that you’re the only business people in the universe attempting to make plan upgrades and downgrades even remotely fair or user friendly. So you’re essentially in uncharted waters here => you probably shouldn’t feel embarrassed about not acing the design on your first try.

Another thing to think about would be “How many months of free Beeminder could I give our current premium users for the price of my salary-hours invested in designing premium plan pricing?” :laughing:


:slight_smile: Yes, @bee and @mary and I were just talking about the startup egg-basket principle and how if very hypothetically we had a time machine it probably would’ve been prudent to nix the whole idea of premium plans altogether. It was a form of diversification from our existing monetization strategy and startups should almost never do that. Again, all totally academic now (as is most of this whole forum thread!) but the amount of time and effort and confusion and whatnot that premium plans have caused has very plausibly exceeded the revenue they’ve brought in, not even counting the additional revenue we could’ve created with all those hours of our lives.

(also true that we made it all way more complicated than we needed to with auto-canceling and half a calculus book’s worth of math in the discount slider, but those are very much the tip of the iceberg in terms of aggregate confusion for us and users, UI complexity, bugs, etc, compared to the universe where there just was no such thing as premium)

PS: If we ever wanted to actually consider scuttling premium plans it would be a pretty huge amount of work, even just deciding for sure that it was a good idea in theory, let alone in practice, with making things fair for people. E.g., what’s the value of being able to say we have a charity option, support savings from paywalling confusing features, etc, etc.

PPS: Probably the efficient/elegant way to implement the grid of checkboxes is with interval trees.


So how does the startup egg-basket principle work and why should startups not offer premium plans?

I can’t imagine how this statement (that is, that the premium plans weren’t worth it) could be true. If you gave all free users all features your revenue would be significantly reduced, as many of the features make it so users don’t have to pay as much in penalties.

On the other hand, if you didn’t even have those features, beeminder would be much less useful, and fewer people would use it in the first place and more of the ones who did would quit. (Beeminder would be useless to me at the free or lowest levels.) Those features are a big part of what attracts users.

It’s also not clear to me that your time and effort could have been used more efficiently by getting more users to pay more penalties for the crippled product beeminder would have been - that is, my guess is that the number of users is a function of beeminder’s and the beeminder community’s organic growth over time, rather than something you can achieve in a big block all at once through having shoved in more hours at a point in time in the past.

Unless you mean charge everyone a constant monthly fee from the beginning? But then you may have gotten much fewer users from the beginning.

But what about the complexity and bugs from the features or other settings you might have added or changed instead, not to mention fielding requests for adding the premium features?

So basically, although I realize I’m very biased because I love the premium system, and I realize there is some status quo bias here, I can’t see how this could be true.

Yeah, this.

Cool - I’ve never seen those! I’m not sure that makes sense for this option with discrete check boxes though, because you have to keep track of the cost of each check box as it changes over time, add up the costs of different check boxes, move the array forward one month every month, and so on, all of which seem to suggest using a 2d array instead, since you’re dealing with monthly rows you’ll be iterating over rather than arbitrary intervals.


Sorry, I was way too vague! The startup egg-basket principle is: put all your eggs in one basket. Most startups should focus exclusively on their premium plans and arguably not have a free plan. Ie, focus on the one most promising thing for making the startup sustainable. Beeminder already had that – pledge revenue – and so, by strict adherence to the egg-basket principle, should’ve doubled down on it.

I think the rest is too hypothetical to worry much about but one example is we could’ve really gotten brazen with the derailing is not failing philosophy and said that you can unlock premium-like features by derailing more. :slight_smile: I think I’m kidding but honestly can’t tell right now!


But it sounds like that wasn’t sustainable without a premium plan, because you’d have to either cripple features, or accept the revenue loss for having those features without having premium plan revenue make up for it.

You’re already doing this partially and informally by offering discounts for premium plans based on derailing pledges, right?

And don’t forget, doing it that way would have its own “aggregate confusion for us and users, UI complexity, bugs, etc.”