Suppose you are trying to predict how many runs an IPL batsman will score in his next innings, using stats like strike rate, recent form, and the venue. You build one decision tree. It gets some players roughly right and others badly wrong — say it predicts 30 runs for a player who actually scores 55. That is a residual (leftover error) of 25 runs. Instead of throwing the tree away and starting over, what if you built a second tree whose entire job is to predict that leftover error of +25 for players like him, and added its output to the first tree's prediction? Then a third tree that predicts whatever error is still left after trees one and two? Keep doing this a few hundred times, each new tree chasing the mistakes of everything built so far, and the sum of all the trees converges on the actual score far better than any single tree could.
That is gradient boosting in one paragraph. XGBoost (eXtreme Gradient Boosting) is a specific, extremely well-engineered way of building that sequence of trees. It was introduced by Tianqi Chen in 2016 (the paper is called “XGBoost: A Scalable Tree Boosting System”) and it went on to win a disproportionate share of Kaggle machine learning competitions for structured/tabular data in the years that followed — not because the idea of boosting was new, but because XGBoost made the math behind each tree rigorous (using calculus most competitors approximated) and the implementation fast enough to run on millions of rows. This chapter derives that math from scratch, so that by the end you can compute, by hand, exactly which split XGBoost would choose and why.
Boosting is not Random Forest — a misconception to kill early
Many students meet Random Forest first and assume XGBoost is “the same thing but fancier.” It is not, and confusing the two costs marks on exams and gets the intuition backwards.
- Random Forest (bagging): builds many trees independently and in parallel, each on a random bootstrap sample of the data, then averages their predictions. Each tree never sees the others' mistakes.
- XGBoost (boosting): builds trees sequentially. Tree 2 is trained specifically to correct the errors that Tree 1 (and everything before it) got wrong. Tree 2 cannot even be built until Tree 1 exists, because it needs Tree 1's residuals as its target.
Bagging reduces variance by averaging out uncorrelated mistakes. Boosting reduces bias by explicitly hunting down whatever mistakes remain. This is why boosted models can reach lower training error than a random forest of the same size — and also why they need careful regularization, which is most of what the rest of this chapter is about.
First, the calculus tool this whole chapter runs on: quadratic approximation
Before we can say precisely what “correcting the error” means, we need one calculus idea: you can approximate almost any smooth curve, near a point, by a parabola that shares its value, its slope, and its curvature at that point. This is called a second-order Taylor expansion, and it is worth seeing it work on a plain number before we ever apply it to a loss function.
Take f(x) = x³ at the point x = 2. We know three things about it there:
- The value: f(2) = 8
- The slope (first derivative): f′(x) = 3x², so f′(2) = 12
- The curvature (second derivative): f″(x) = 6x, so f″(2) = 12
The claim is: for a small step Δ away from x = 2,
f(2 + Δ) ≈ f(2) + f'(2)·Δ + (1/2)·f''(2)·Δ²
Let's test it numerically with Δ = 0.1, so we're approximating f(2.1):
approx = 8 + 12(0.1) + 0.5(12)(0.1)²
= 8 + 1.2 + 0.06
= 9.26
actual: f(2.1) = 2.1³ = 9.261
The approximation is off by only 0.001 — because x³ has a nonzero third derivative, the parabola isn't a perfect match, just an excellent local one. Now notice what would happen if f were itself a parabola, say f(x) = ax²+bx+c: its third derivative is exactly zero everywhere, so this “approximation” would be exact, not approximate, for any step size. Hold onto that fact — it is the reason XGBoost's math works out so cleanly for the most common loss function.
The other piece we need is: given a parabola g(w) = aw² + bw + c with a > 0, where is its minimum? Set the derivative to zero and solve:
g'(w) = 2aw + b = 0
w* = -b / (2a)
That single line — “set the derivative of a parabola to zero to find its minimum” — is the entire optimization XGBoost performs at every single leaf of every single tree. Everything below is just applying these two facts (quadratic approximation, and minimizing a parabola) to the specific problem of correcting prediction errors.
Turning “how far off” into a gradient and a Hessian
We need a precise number for “how wrong” a prediction is. Use squared error, written with a factor of one-half in front purely to keep the derivative clean:
l(y, ŷ) = (1/2)(y - ŷ)²
where y is the true value (actual runs scored) and ŷ (“y-hat”) is the model's current prediction. Treat l as a function of ŷ alone (y is just a fixed number once we know the player's actual score) and differentiate, exactly as in the warm-up above:
g = ∂l/∂ŷ = -(y - ŷ) = ŷ - y (first derivative — the "gradient")
h = ∂²l/∂ŷ² = 1 (second derivative — the "Hessian")
Read g carefully: it is the prediction minus the actual value. If we over-predicted (ŷ > y), g is positive. If we under-predicted, g is negative. g literally tells the next tree which direction and how far to nudge the prediction, and h (here just a constant, 1, for squared error) tells it how much to trust that nudge. This g, h pair is computed independently for every training example, using whatever the ensemble's prediction is so far — not from scratch each time.
Why bother with derivatives instead of just using the raw residual (y − ŷ) directly, which is simpler? Because XGBoost is built to work with any twice-differentiable loss function, not just squared error — log-loss for classifying whether a batsman gets out, ranking losses for ordering a batting lineup, and so on. The gradient-and-Hessian recipe is identical no matter which loss you choose; only the formulas for g and h change. That generality, combined with the engineering that makes it fast at scale, is what “eXtreme” in the name is pointing at.
Expanding the objective for one new tree
Say the ensemble already has predictions ŷi(t−1) for every training example i, built from trees 1 through t−1. We want to add tree t, whose output on example i we'll call ft(xi), so the new prediction becomes ŷi(t−1) + ft(xi). The total objective we want to minimize, summed over all examples, plus a complexity penalty Ω on the new tree, is:
Obj = ∑_i l( y_i , ŷ_i^(t-1) + f_t(x_i) ) + Ω(f_t)
Apply the quadratic (second-order Taylor) approximation from the warm-up section to each term of the sum, expanding around the existing prediction ŷi(t−1), with ft(xi) playing the role of the small step Δ:
l( y_i , ŷ_i^(t-1) + f_t(x_i) ) ≈ l(y_i, ŷ_i^(t-1)) + g_i·f_t(x_i) + (1/2)h_i·f_t(x_i)²
The first term, l(yi, ŷi(t−1)), doesn't involve ft at all — it's a constant as far as choosing the new tree is concerned, so we can drop it when minimizing. Because our loss is squared error, this expansion is exact (recall: zero third derivative), so no accuracy is lost by dropping higher-order terms.
Now define the complexity penalty. A tree with T leaves, where leaf j outputs the constant value wj, is penalized as:
Ω(f_t) = γT + (1/2)λ∑_{j=1}^{T} w_j²
γ charges a flat cost per leaf (discouraging trees with too many leaves), and λ is an L2 penalty that shrinks leaf weights toward zero (discouraging any single leaf from making an extreme prediction). Both are hyperparameters you choose before training.
Collapsing the sum by leaf
Every training example lands in exactly one leaf of tree t. So instead of summing over examples one at a time, group them by which leaf they fall into. Let Ij be the set of examples in leaf j, and define:
G_j = ∑_{i ∈ I_j} g_i (sum of gradients of examples in leaf j)
H_j = ∑_{i ∈ I_j} h_i (sum of Hessians of examples in leaf j)
Since every example in leaf j gets the same output ft(xi) = wj, the objective (dropping the constant term and substituting Ω) becomes a sum over leaves instead of examples:
Obj ≈ ∑_{j=1}^{T} [ G_j w_j + (1/2)(H_j + λ) w_j² ] + γT
Look closely: each leaf contributes a term of the form a·wj² + b·wj, with a = (1/2)(Hj+λ) and b = Gj — exactly the parabola from the warm-up section, one independent parabola per leaf. And because the leaves don't interact (leaf j's weight has no effect on leaf k's term), we can minimize each one separately using the vertex formula w* = −b/(2a) derived earlier:
w_j* = -G_j / (H_j + λ)
This is the optimal prediction for every leaf in an XGBoost tree. Notice the sign: if a leaf's examples were systematically under-predicted before this tree (Gj negative, since gi = ŷi − yi), then wj* comes out positive — the new tree pushes the prediction up, exactly correcting the error. λ sits in the denominator purely as a brake: larger λ shrinks wj* toward zero regardless of how large Gj is, which is precisely what “regularization” means here.
Substituting wj* back into the objective gives the best possible score for a tree with this particular leaf structure (a smaller number is a better tree):
Obj* = -(1/2) ∑_{j=1}^{T} G_j² / (H_j + λ) + γT
This is called the structure score. It lets you score an entire tree shape without ever fitting actual weight values first — which is exactly what you need to decide, split by split, whether growing the tree further is worth it.
Deriving the Gain formula for a candidate split
Suppose a leaf with gradient/Hessian sums G and H is a candidate to be split into a left child (GL, HL) and right child (GR, HR), where GL+GR=G and HL+HR=H. The structure score before the split (one leaf) is −(1/2)·G²/(H+λ) + γ. After the split (two leaves) it is −(1/2)[GL²/(HL+λ) + GR²/(HR+λ)] + 2γ. Splitting is worthwhile only if it lowers the structure score, so define Gain as (score before) minus (score after):
Gain = (1/2) [ G_L²/(H_L+λ) + G_R²/(H_R+λ) - (G_L+G_R)²/(H_L+H_R+λ) ] - γ
Every term in this formula already accounts for γ — the −γ is the net cost of turning one leaf into two (each split adds exactly one extra leaf, and γ is charged per leaf, so the increase in leaf count contributes a +γ to the after-split score, which becomes −γ in Gain). This means the decision rule is simply: split if Gain > 0, and don't split otherwise. There is no separate comparison against γ afterwards — γ has already done its job inside the formula. Every candidate split (every feature, every threshold) is scored this way, and XGBoost picks whichever split has the largest Gain, splitting only if that largest Gain is positive.
Worked example: should we split on strike rate?
Back to predicting IPL batsman runs. A tree-growing algorithm is considering a leaf with 6 players and is testing whether splitting on “strike rate ≤ 140 vs. > 140” improves things. Using squared-error loss, every hi = 1, so H for a group is just its player count. Suppose the gradients (gi = ŷi − yi, using the ensemble's predictions so far) work out to:
- Left (strike rate ≤ 140), 3 players: g values −8, −5, −3, so GL = −16, HL = 3
- Right (strike rate > 140), 3 players: g values 4, 6, 9, so GR = 19, HR = 3
Take λ = 1 and γ = 5. First, the optimal weight each side would get if we split:
w_L* = -(-16)/(3+1) = 4.0 (left side was under-predicted → push predictions up by 4)
w_R* = -(19)/(3+1) = -4.75 (right side was over-predicted → pull predictions down by 4.75)
This already makes intuitive sense before we even compute Gain: the low-strike-rate players were scoring more than predicted, the high-strike-rate players were scoring less, so a split that treats them differently should help. Now the Gain itself:
term_L = G_L²/(H_L+λ) = 256/4 = 64.0
term_R = G_R²/(H_R+λ) = 361/4 = 90.25
term_combined = (G_L+G_R)²/(H_L+H_R+λ) = 3²/7 = 9/7 ≈ 1.2857
raw score reduction = term_L + term_R - term_combined
= 64.0 + 90.25 - 1.2857
≈ 152.9643
Gain = (1/2)(152.9643) - γ
= 76.4821 - 5
≈ 71.48
Gain ≈ 71.48 > 0, so the split happens. Notice the two separate numbers here: the raw score reduction (152.9643, or half of it, 76.48, before any γ is subtracted) measures how much purely statistical improvement the split buys; Gain (71.48) is what's left after paying the fixed complexity cost γ for adding a leaf. If we had instead set γ = 100, Gain would become 76.48 − 100 = −23.52 < 0, and the algorithm would refuse this split — correctly, since γ acting as a threshold on the raw score reduction is exactly equivalent to requiring Gain > 0.
Here is the same computation as code, first with gamma = 0 to see the raw reduction on its own, then with gamma = 5 to see the actual Gain XGBoost would use:
def split_gain(G_L, H_L, G_R, H_R, lam, gamma):
left = (G_L ** 2) / (H_L + lam)
right = (G_R ** 2) / (H_R + lam)
combined = (G_L + G_R) ** 2 / (H_L + H_R + lam)
return 0.5 * (left + right - combined) - gamma
raw = split_gain(-16, 3, 19, 3, lam=1, gamma=0)
print(raw) # 76.48214285714286
gain = split_gain(-16, 3, 19, 3, lam=1, gamma=5)
print(gain) # 71.48214285714286
Trace it by hand: left = 256/4 = 64.0, right = 361/4 = 90.25, combined = 9/7 = 1.2857142857142858. 0.5 * (64.0 + 90.25 - 1.2857142857142858) = 0.5 * 152.96428571428572 = 76.48214285714286. Subtracting gamma=5 gives 71.48214285714286, matching the hand calculation above and confirming Gain > 0.
Why not just grow every split with positive Gain forever?
In principle, XGBoost evaluates every candidate threshold on every feature at every leaf, computes Gain for each, and greedily takes the best one, repeating until no split has positive Gain or a maximum depth is reached. Left unchecked, this happily grows leaves that fit individual training examples' noise. Three separate levers keep this in check, each doing a distinct job you should be able to tell apart:
- γ stops a split from happening at all unless it earns back more than the fixed per-leaf complexity cost — it prunes based on structure, evaluated exactly once, before the split is made.
- λ doesn't block splits; it shrinks every leaf's weight wj* = −Gj/(Hj+λ) toward zero, so even leaves that do get created make quieter, more conservative predictions.
- Learning rate η (also called shrinkage) is applied outside all of this math: instead of adding the full tree's predictions to the ensemble, XGBoost adds η·ft(x) for some small η (commonly 0.01–0.3), so each tree only nudges the prediction a little, leaving room for hundreds of later trees to keep refining it rather than one tree overcommitting.
Get comfortable distinguishing these three, because exam questions frequently swap them: γ prunes structure, λ dampens weights, η paces the whole sequence.
What the diagram below shows
The figure traces three boosting rounds for a single player whose actual score is 62 runs. Tree 1 alone predicts 30 (a residual of 30 − 62 = −32 before rounding for display). Tree 2 is trained on that residual and adds a correction of +20, bringing the running prediction to 50. Tree 3 is trained on the new, smaller residual and adds +9, bringing the running total to 59 — each tree's job gets easier as the ensemble gets closer to the truth, which is the entire point of “boosting.”
Where this connects to your curriculum
CBSE's Artificial Intelligence skill subject (Code 417) for Classes IX–X introduces the idea of ensemble learning and decision trees at a conceptual level; its Classes XI–XII continuation (Code 843) goes further into model evaluation and advanced classifiers, where boosting is typically discussed by name even if the second-order derivation above isn't required at that stage. The general skill this chapter builds — expanding a function using its first and second derivatives, then minimizing a resulting quadratic by setting its derivative to zero — is standard Class 11–12 calculus and appears constantly in physics (small-oscillation approximations) and in the optimization sections of your math syllabus, so the practice here pays off well beyond machine learning specifically. IIT-JEE and BITSAT do not test XGBoost or ensemble methods directly — their syllabi are physics, chemistry, and mathematics — but the comfort with Taylor expansion and quadratic minimization you build here is the same comfort those exams' calculus sections reward. Further out, if you pursue engineering and eventually GATE's Data Science and Artificial Intelligence paper, this exact derivation (gradient boosting, structure score, split gain) reappears as core, examinable material — useful to know is waiting for you, not urgent to memorize now.
Check your understanding
- A leaf has gradient sum G = −20 and Hessian sum H = 5, with λ = 2. What is the optimal weight w* for this leaf, and does the sign make sense given that gi = ŷi − yi?
- Explain in one sentence why Random Forest trees can be built in parallel but XGBoost trees cannot.
- For squared-error loss l = (1/2)(y − ŷ)², why is the second-order Taylor expansion of the objective exact rather than approximate?
- A candidate split has GL = −10, HL = 2, GR = 14, HR = 2, with λ = 1. Compute the raw score reduction (i.e. Gain with γ = 0). If γ = 20, does XGBoost make this split? What is the largest integer value of γ for which it still would?
- A different candidate split has a raw score reduction (before subtracting γ) of 20. With γ = 15, what is the Gain, and does XGBoost make the split?
- Why does increasing λ shrink every leaf's weight toward zero regardless of how large |Gj| is? (Hint: look at where λ sits in wj* = −Gj/(Hj+λ).)
Answers: (1) w* = −(−20)/(5+2) = 20/7 ≈ 2.857; positive, meaning this leaf was under-predicting on average (G < 0), so the correction correctly pushes predictions up. (2) Each new XGBoost tree is trained on the residuals of the current ensemble, which only exist once all previous trees have been added, creating a strict dependency; Random Forest trees each train independently on bootstrap samples with no such dependency. (3) Because the third and all higher derivatives of a quadratic function (which l is, in ŷ) are exactly zero, so the Taylor series terminates after the quadratic term with no error left over. (4) raw = (1/2)[100/3 + 196/3 − 16/5] − 0 = (1/2)[33.33 + 65.33 − 3.2] = (1/2)(95.467) ≈ 47.73; with γ = 20, Gain ≈ 27.73 > 0, so it splits; the largest integer γ still allowing a split is 47 (Gain ≈ 0.73 > 0), since γ = 48 would make Gain negative. (5) Gain = 20 − 15 = 5 > 0, so it splits. (6) λ is added directly into the denominator (Hj+λ); a larger denominator divides Gj down toward zero no matter how large the numerator is, which is exactly what an L2-style penalty is meant to do.
Summary
XGBoost builds trees sequentially, each one trained to correct the errors of everything built before it — unlike Random Forest, whose trees are independent and parallel. To decide what each new tree should predict, it approximates the loss function around the current prediction using a second-order Taylor expansion (exact for squared error, approximate but still very effective for other losses), reducing the problem at every leaf to minimizing a simple parabola in the leaf weight w. Setting that parabola's derivative to zero gives the optimal leaf weight wj* = −Gj/(Hj+λ), and comparing the total objective before and after a candidate split gives the Gain formula, Gain = (1/2)[GL²/(HL+λ) + GR²/(HR+λ) − (GL+GR)²/(HL+HR+λ)] − γ, where γ is already netted out — so the rule is simply split when Gain is positive. Three separate hyperparameters keep the resulting trees from overfitting: γ blocks low-value splits outright, λ shrinks leaf weights toward zero, and the learning rate η paces how much of each tree's correction actually gets added to the running prediction.