Skip to content
Michael Malura
570 GitHub stars 5810 npm downloads 53 Projects

XP curves

How much experience does the next level cost? It sounds like a number you just make up, and that is usually how it starts: level 2 costs 100, level 3 costs 200, we'll see after that. By level 40 at the latest you notice that the curve is the actual game design. It decides how long a level feels and when a player gives up.

Two curves are drawn at the top: in orange what the next level costs, in teal the total you need to be a given level. Chips switch between five curve types, the sliders a and b change them. "Log y" makes the y axis logarithmic, otherwise the steep curves show up as a flat line with a hook at the end. Below it the same curve is shown as play time, for a chosen amount of XP per hour. Hover the plots and a card shows level, cost, total XP and hours. A little player collects XP the whole time and climbs the curve, x10 or x100 makes that faster.

Cost and total

I always work with two functions. step(L) is the XP to get from level L to L+1. total(L) is the sum of all steps before it, the XP you need to be level L. Level 1 has 0.

The linear curve is step(L) = a * L, the polynomial one a * L^b, the exponential one a * b^L. The table has eleven hand-picked anchor values, in between it interpolates geometrically and rounds to tens, the way you would write it down in a spreadsheet.

The linear curve feels reasonable at the start. With a = 2500 and 100,000 XP per hour, level 1 takes a minute and a half and level 98 just under two and a half hours. It grows evenly, and that is exactly the problem: once the player earns faster with better gear and stronger enemies, the levels start running away from them. If every level should take roughly the same time, the cost has to grow as fast as the earning rate. The demo keeps the rate fixed, so the dashed line at the bottom shows what the total time would look like if every level took the same time. All curves here lie below it and bend steeply upwards at the end.

The RuneScape trick

The best known curve is the one from RuneScape. Every level has a points value, the XP are the sum of those divided by 4:

inline double runescapePoints(int l) { return std::floor(static_cast<double>(l) + 300.0 * std::exp2(static_cast<double>(l) / 7.0)); }

sum += static_cast<std::int64_t>(detail::runescapePoints(l));
totals_[static_cast<std::size_t>(l)] = sum / 4;

The 2^(l/7) is the core. Each level the step grows by a factor of 2^(1/7), about 10.4 percent, and every 7 levels it has doubled. That is why level 92 needs 6,517,253 XP and level 99 almost exactly twice as much, 13,034,431. Halfway to 99 is level 92. At 100,000 XP per hour you are level 50 after a good hour and level 92 after 65 hours. The last level costs 1,228,825 XP, a little over 12 hours. Level 1 to 2 is 83 XP, a few seconds.

I recomputed the formula. The test checks level 2 (83), level 10 (1,154), level 50 (101,333) and level 99 (13,034,431), and they all match.

From XP to level

Every game needs the other direction all the time: the player has 3.2 million XP, which level is that? You could try total(L) for every L. I put the thresholds into an array once and use a binary search:

const auto it = std::upper_bound(first, last, xp);
return std::max(1, static_cast<int>(it - first));

upper_bound returns the first level whose threshold is above the XP. One before it is the player's level. Exactly on the threshold it is already the new level, one XP below it is still the old one, and 0 XP is level 1. The test says exactly that. For the pure exponential curve there is an inverse formula (a logarithm), but with a table, with the rounded RuneScape values, or as soon as somebody bends the curve, the lookup table with a binary search is the way that always works. With 99 entries that is seven comparisons.

Pitfalls

Round down only at the end. If you round every points value down after dividing by 4 and then add them up, level 10 lands at 1,151 instead of 1,154 and level 99 at 13,034,394. Sum the whole points first, then divide by 4.

Exponential curves blow through 64 bit faster than you think. 10 * 2^99 has more than thirty digits. So every step is capped at 4 quadrillion, and 98 of those at 3.9e17 still fit comfortably into an int64 (maximum 9.2e18), so the sum never overflows:

inline std::int64_t toStep(double raw) {
    if (std::isnan(raw) || raw < 1.0) return 1;
    if (!(raw < static_cast<double>(kMaxStep))) return kMaxStep;
    return static_cast<std::int64_t>(std::llround(raw));
}

The raw < 1.0 has a second job. With a = 0 every level would be free, all thresholds would be 0 and the binary search would have no unique answer. So every step costs at least 1 XP. For the exponential curve a b smaller than or equal to 1 counts as 1, otherwise the steps would shrink.

The little player runs in fixed steps of a sixtieth of a second, like in Fixed Timestep. At x100 many levels pass per second, so every step compares the level before and after the gain and fires the pulse when it went up. I build the thousands separators myself, snprintf with a locale would print dots or commas depending on the system.

You can find the code in the repo under xp-curves. All demos of the series are collected in the project Game Dev Snippets.

23.10.2026 updated 06.11.2026
Share X Reddit Hacker News LinkedIn E-Mail
Subscribe to new posts RSS JSON