Decision Trees and Recursive Partitioning

Imagine a bank deciding whether to approve a loan. A human loan officer might ask: is income above $50,000? If yes, is the applicant's debt-to-income ratio below 30%? If yes, approve. This chain of simple yes/no questions, each one narrowing down the possibilities, is exactly what a decision tree learns automatically from data. Instead of a human hand-crafting the rules, the algorithm discovers which questions to ask. It also learns what order to ask them in. It does this by searching through the training data for splits that best separate outcomes. Decision trees matter because they sit at a rare intersection of accuracy, speed, and interpretability. A trained tree can be read directly as a set of if-then rules. This matters enormously in regulated domains like credit scoring, medical diagnosis, and criminal justice risk assessment. In these fields, a black-box prediction is not legally or ethically sufficient. Trees also require almost no data preprocessing. There is no feature scaling, and no handling of mixed categorical and continuous variables. They naturally model interactions and non-linear boundaries that linear models miss entirely. The core mechanism is recursive partitioning: repeatedly slice the feature space into smaller rectangular regions. At each step, choose the cut that makes the resulting groups as internally consistent as possible with respect to the target variable. This greedy, top-down process builds a hierarchy of decisions. That hierarchy can be visualized as a tree and read as a flowchart. A single tree is rarely the most accurate model available. But it is the conceptual and computational foundation for far more powerful ensemble methods. This article develops the mechanics of building a decision tree from first principles. It covers how splits are chosen, and how the recursion terminates. It also covers what it costs computationally, and where the naive greedy approach can go badly wrong. It sets up the machinery — impurity measures, stopping rules, tree structure — that later articles build on directly. This is particularly true when quantifying split quality with information theory. It also applies when controlling tree size to avoid overfitting.

Key takeaways
  • Decision trees recursively split feature space into axis-aligned rectangular regions, each assigned a single prediction.
  • Splits are chosen greedily at each node to maximize a purity gain — the tree never looks ahead or backtracks.
  • Growing continues until a stopping condition (max depth, minimum samples, pure node) halts recursion at each branch.
  • Training costs roughly O(n⋅d⋅nlog⁡n)O(n \cdot d \cdot n\log n) time for nn samples and dd features; prediction costs O(depth)O(\text{depth}).
  • Unconstrained trees overfit almost guaranteed; depth limits, minimum leaf size, or pruning are essential in practice.
  • Trees are the building block for random forests, gradient boosting, and other ensemble methods covered elsewhere.
The core insight

A decision tree predicts by greedily and recursively splitting feature space along single-axis thresholds until each resulting region is as pure as possible.

The idea

A decision tree is a hierarchical model that partitions the input feature space X⊆Rd\mathcal{X} \subseteq \mathbb{R}^d into a set of disjoint axis-aligned regions R1,…,RMR_1, \dots, R_M, and assigns a constant prediction y^m\hat{y}_m to every point falling in region RmR_m. Structurally, the tree is a rooted binary (or occasionally multi-way) tree in which every internal node stores a split rule of the form "feature jj ≤\le threshold tt", and every leaf node stores a prediction — a class label or class-probability vector for classification, a real number (typically the mean of training targets in that region) for regression. The defining algorithmic technique is recursive partitioning, also called recursive binary splitting. Starting from the full training set at the root, the algorithm searches over all features and all candidate thresholds for the single split that best improves some impurity or loss criterion, applies that split to divide the data into two child nodes, and then repeats the entire search independently and recursively on each child. Because the algorithm commits to the locally best split at every step without reconsidering earlier choices, it is a greedy algorithm: it never backtracks to ask whether a different split earlier in the tree would have led to a better tree overall. The impurity criterion quantifies how "mixed" the labels are within a node. For classification, common choices are the Gini index Gini(p)=∑kpk(1−pk) \text{Gini}(p) = \sum_k p_k(1-p_k) and entropy −∑kpklog⁡pk-\sum_k p_k \log p_k, both minimized at zero when a node is pure (all one class) and maximized when classes are equally mixed. For regression, the natural criterion is the residual sum of squares (variance) within a node. The tree's job at each split is to choose the feature and threshold that most reduces this impurity, weighted by the size of the resulting children — a quantity closely related to information gain, developed in depth elsewhere. Because splits are always of the form "one feature compared to one threshold," the resulting regions are always axis-aligned hyper-rectangles — a geometric property with real consequences for what shapes of decision boundary a tree can and cannot represent efficiently.

How it works

Building a tree requires two nested procedures: a routine that finds the best single split for a given node's data, and a routine that recursively applies this splitting until some stopping condition is met. Prediction, given a fitted tree, is simply a root-to-leaf walk following the split rules.

Finding the best split. For every candidate feature jj and every candidate threshold tt (in practice, the midpoints between consecutive sorted distinct values of feature jj), the algorithm partitions the current node's samples into a left group (xj≤tx_j \le t) and a right group (xj>tx_j > t), computes the impurity of each group, and combines them into a weighted impurity:

Q(j,t)=nLn Impurity(DL)+nRn Impurity(DR)Q(j, t) = \frac{n_L}{n} \, \text{Impurity}(D_L) + \frac{n_R}{n} \, \text{Impurity}(D_R)

The split (j∗,t∗)(j^*, t^*) chosen is the one minimizing Q(j,t)Q(j,t), equivalently maximizing the impurity reduction relative to the parent node.

Recursive growth. Once the best split is found, the node's data is divided accordingly, and the same search-and-split procedure is invoked independently on each child. The recursion halts at a node — turning it into a leaf — when any of several stopping conditions holds: the node is already pure, it has fewer samples than a minimum threshold, the tree has reached a maximum depth, or no candidate split improves impurity by more than some tolerance. ``` function BUILD_TREE(D, depth):

# D: set of (x, y) training pairs at this node if STOPPING_CONDITION(D, depth): return LEAF(predict = AGGREGATE(D)) # majority class or mean target best_gain <- -infinity best_split <- None for each feature j in 1..d: sort D by feature j # O(n log n) per feature for each candidate threshold t (midpoints of adjacent sorted values): D_L, D_R <- partition D by (x_j <= t) if D_L empty or D_R empty: continue gain <- IMPURITY(D) - WEIGHTED_IMPURITY(D_L, D_R) if gain > best_gain: best_gain <- gain best_split <- (j, t, D_L, D_R) if best_gain <= 0: # no useful split found return LEAF(predict = AGGREGATE(D)) (j, t, D_L, D_R) <- best_split left_child <- BUILD_TREE(D_L, depth + 1) right_child <- BUILD_TREE(D_R, depth + 1) return NODE(feature = j, threshold = t, left = left_child, right = right_child)

function PREDICT(tree, x): if tree is a LEAF: return tree.predict if x[tree.feature] <= tree.threshold: return PREDICT(tree.left, x) else: return PREDICT(tree.right, x)

function STOPPING_CONDITION(D, depth): return (IMPURITY(D) == 0) # pure node or (|D| < min_samples_split) # too few samples or (depth >= max_depth) # depth limit reached ``` A subtlety worth making explicit: `IMPURITY(D)` for classification is typically Gini or entropy over the class distribution in `D`; for regression it is the variance of the target values in `D`, and `AGGREGATE` returns the mean rather than a majority vote. The algorithm is otherwise identical across both settings — this is why decision trees are often described as a single unified framework rather than two separate methods (CART, Classification and Regression Trees, was designed explicitly this way). The diagram below shows a tiny fitted tree alongside the feature-space partition it induces, making explicit that every leaf corresponds to exactly one axis-aligned rectangle.

Figure
A depth-2 decision tree and the rectangular regions it carves out of feature space
Income ≤ 50k? root Leaf: Deny DTI ≤ 30%? node Leaf: Approve Leaf: Deny Deny Deny Approve Income → DTI →

Complexity & correctness

Time complexity. At a single node containing nn samples and dd features, evaluating all candidate splits for one feature requires sorting that feature's values (O(nlog⁡n)O(n \log n)) and then a single linear scan accumulating class counts (or sum/sum-of-squares for regression) to evaluate impurity at each candidate threshold in O(n)O(n) total. Across dd features this is O(d⋅nlog⁡n)O(d \cdot n \log n) work per node. Summing over all nodes at a single depth level touches every training sample exactly once collectively (each sample lives in exactly one node per depth level), giving O(d⋅nlog⁡n)O(d \cdot n \log n) work per level. With DD levels of depth, total training time is

O(D⋅d⋅nlog⁡n).O(D \cdot d \cdot n \log n).

If the tree is grown to full depth on non-degenerate data, D=O(log⁡n)D = O(\log n) in the balanced case, giving O(d nlog⁡2n)O(d\, n \log^2 n); in the worst case (highly unbalanced splits, e.g. one sample peeled off per level) D=O(n)D = O(n), giving a much worse O(d n2log⁡n)O(d\, n^2 \log n). In practice, presorting each feature once at the root and reusing sorted order via masks through the recursion (rather than re-sorting at every node) is a standard optimization that removes the repeated log⁡n\log n factor, common in production implementations.

Space complexity. Storing the tree itself costs O(number of nodes)O(\text{number of nodes}), which is at most O(n)O(n) since a tree grown to purity has at most nn leaves (each leaf must contain at least one sample). Auxiliary space during training for sorted indices is O(n⋅d)O(n \cdot d).

Prediction complexity. A single prediction walks one root-to-leaf path, costing O(depth)O(\text{depth}), which is O(log⁡n)O(\log n) for a balanced tree and O(n)O(n) in the worst case. This is why tree depth is not just a statistical concern (overfitting) but also a latency concern in deployed systems.

Correctness — in what sense is the greedy algorithm "correct"? Finding the globally optimal decision tree (the tree, among all trees of a given size, that minimizes training impurity) is known to be NP-hard even for simple impurity measures — the search space of possible partitions grows combinatorially with depth. Recursive partitioning does not solve this problem exactly; it is explicitly a greedy heuristic. Its "correctness" claim is therefore weaker and more honest: at each step, it computes the split that is provably optimal for that node in isolation, because the split search exhaustively enumerates every feature and every distinct threshold and evaluates the exact impurity reduction — there is no approximation within a single split decision. The invariant maintained throughout the recursion is: > At every node, the chosen split is the impurity-minimizing split among all axis-aligned single-feature thresholds, given the data that has already been routed to that node by earlier splits. This invariant guarantees local optimality at each decision but not global optimality of the resulting tree, since an early split that looks locally weak might have enabled a much stronger downstream split that the greedy algorithm never considers. The practical justification for using greedy search anyway is computational: exhaustive search over all possible tree structures of depth DD is exponential in DD, while greedy recursive partitioning is polynomial, as derived above — an enormous, usually decisive, efficiency gain for a small, well-documented loss in optimality.

Monotonic impurity decrease. One can prove that impurity is non-increasing as splits accumulate: since the algorithm only accepts a split when Q(j,t)<Impurity(parent)Q(j,t) < \text{Impurity}(\text{parent}) (equivalently `best_gain > 0`), every accepted split strictly decreases weighted impurity, and impurity is bounded below by zero. This guarantees the recursion terminates (impurity cannot decrease forever) and that training-set impurity is a non-increasing function of tree depth — the formal reason why an unconstrained tree can always be driven to zero training error (each leaf reduced to single-sample purity), which is precisely the mechanism behind decision-tree overfitting.

Worked example

Consider a small dataset for predicting whether a customer churns, using two features: tenure (months) and monthly charges ($).

CustomerTenureMonthly chargesChurn?
A290Yes
B395Yes
C2440No
D3045No
E130Yes
F40100No

The root node contains 6 samples: 3 "Yes", 3 "No". Using Gini impurity, Gini(root)=1−(0.52+0.52)=0.5 \text{Gini}(\text{root}) = 1 - (0.5^2 + 0.5^2) = 0.5.

Step 1 — evaluate splits on Tenure. Sorted tenure values: 1, 2, 3, 24, 30, 40. Candidate thresholds are midpoints: 1.5, 2.5, 13.5, 27, 35. Consider threshold t=13.5t = 13.5 (Tenure ≤13.5\le 13.5): left group = {A, B, E} all "Yes" (Gini=0\text{Gini}=0); right group = {C, D, F} all "No" (Gini=0\text{Gini}=0). Weighted impurity:

Q=36(0)+36(0)=0Q = \frac{3}{6}(0) + \frac{3}{6}(0) = 0

This is a perfect split — impurity drops from 0.5 to 0, a gain of 0.5, the maximum possible.

Step 2 — evaluate splits on Monthly charges (for comparison). Sorted values: 30, 40, 45, 90, 95, 100. Threshold t=67.5t=67.5: left = {C, E, D} → labels {No, Yes, No}, Gini =1−((2/3)2+(1/3)2)=0.444 = 1 - ((2/3)^2 + (1/3)^2) = 0.444; right = {A, B, F} → {Yes, Yes, No}, Gini =0.444=0.444. Weighted impurity Q=0.444Q = 0.444, gain only 0.5−0.444=0.0560.5 - 0.444 = 0.056 — far worse than the Tenure split.

Step 3 — select best split. The algorithm scans all thresholds on both features (and would continue checking 1.5, 2.5, 27, 35 on Tenure and the remaining thresholds on Monthly Charges), but none beats the perfect gain of 0.5 found at Tenure ≤13.5\le 13.5. The root split is therefore Tenure ≤13.5\le 13.5.

Step 4 — recurse. Both resulting children ({A,B,E} and {C,D,F}) are already pure (Gini =0=0), so `STOPPING_CONDITION` triggers immediately for each, and they become leaves: "Tenure ≤13.5\le 13.5 → predict Churn = Yes" and "Tenure >13.5> 13.5 → predict Churn = No". The final tree has depth 1 with two leaves — despite two candidate features, the algorithm needed only one split to perfectly classify this training set, illustrating how a single well-chosen threshold can dominate the search even when other features look superficially informative. A held-out customer with Tenure = 15, Monthly charges = 85 would be routed right at the root (15 > 13.5) and predicted No churn — notice this prediction ignores the high monthly charge entirely, since the tree never needed that feature on the training data, a reminder that trees only use features that improved impurity on the data they were built from.

Common pitfalls & traps

PitfallWhy it happensConsequence
Growing to full depth with no stopping ruleGreedy splitting can always separate samples until each leaf has one pointZero training error, poor test accuracy (overfitting)
Using accuracy instead of Gini/entropy as split criterionAccuracy is insensitive to probability shifts within a nodeFails to distinguish good splits from bad ones when no split changes majority class
Treating categorical variables as ordered numerics naivelyEncoding categories as arbitrary integers implies a false orderingSplits become meaningless or suboptimal for nominal features
Ignoring bias toward high-cardinality featuresFeatures with many distinct thresholds have more chances to appear to reduce impuritySpurious splits on ID-like columns with no real predictive value
Comparing tree structure across random seeds and expecting stabilitySmall data perturbations can flip the locally-best split at the root, cascading differences downstreamHigh variance between runs, motivating ensemble methods
Forgetting to handle empty child after a splitA candidate threshold at a dataset boundary can produce an empty groupDivision by zero or undefined impurity if not explicitly checked

Pioneers & history

The recursive-partitioning idea for prediction was formalized independently by statisticians in the 1960s–80s, converging on the algorithms still taught today.

Origins · 1 minLeo Breiman and how this was discovered
LB
Leo Breiman
1928–2005 · Statistics, UC Berkeley

Leo Breiman co-authored Classification and Regression Trees (1984) with Jerome Friedman, Richard Olshen, and Charles Stone, formalizing CART — the greedy, impurity-minimizing recursive splitting algorithm still in near-universal use. Breiman later invented Random Forests (2001), building directly on the tree-growing machinery described here to create one of the most widely deployed ensemble methods in applied machine learning.

History
1963
Morgan and Sonquist introduce AID (Automatic Interaction Detection), an early recursive-splitting regression tree method.
1979
Ross Quinlan develops ID3, a decision tree algorithm for classification using entropy-based information gain.
1984
Breiman, Friedman, Olshen, and Stone publish CART, unifying classification and regression trees with Gini impurity and cost-complexity pruning.
1993
Quinlan releases C4.5, extending ID3 with pruning and handling of continuous and missing attributes.
2001
Breiman introduces Random Forests, showing that averaging many decorrelated trees dramatically reduces the variance of a single tree.

Where this matters

Decision trees underpin some of the most heavily used machine learning software in production today, largely as components of ensembles rather than as standalone models. Gradient-boosted tree libraries such as XGBoost, LightGBM, and CatBoost — workhorses of tabular-data competitions and industrial systems for fraud detection, click-through-rate prediction, and credit risk scoring — build hundreds or thousands of shallow trees sequentially, each correcting the errors of the last. Random forests, used widely in bioinformatics, remote sensing, and recommendation systems, average many independently grown deep trees to control variance. Standalone single trees remain popular wherever regulatory or human interpretability requirements demand an explicit, auditable decision path, such as clinical decision support tools and certain credit-approval pipelines. The core mechanics described here — split search, impurity reduction, recursive growth, and the overfitting risk of unconstrained depth — are exactly the mechanics that later articles refine: quantifying split quality with Entropy and Information Gain, controlling tree complexity through Tree Pruning and Complexity Control, and reducing variance by combining many trees via Bagging and the Bootstrap.

Ask the tutor: How does changing the impurity measure from Gini to entropy actually change which splits a decision tree chooses in practice?

Why do decision trees use axis-aligned splits instead of diagonal or curved boundaries?

Axis-aligned splits ("feature j≤tj \le t") are chosen because they can be searched exhaustively and efficiently: for a single feature, sorting the values and sweeping a threshold takes O(nlog⁡n)O(n \log n) time total across all candidate cuts. Diagonal splits (linear combinations of features) would require searching over a continuous space of coefficient vectors per node, which is far more expensive and has no closed-form efficient solution analogous to the sort-and-sweep trick. The tradeoff is real: axis-aligned trees need many splits to approximate a diagonal decision boundary, producing "staircase" approximations, which is one reason ensembles and specialized oblique-tree variants exist.

How does a decision tree handle categorical features that have no natural ordering?

For a categorical feature with kk possible values, an exact split search considers all 2k−1−12^{k-1}-1 ways to partition the categories into two non-empty groups (up to symmetry), rather than a single threshold. For binary classification and certain regression losses, there is an efficient shortcut: sorting the categories by their mean target value reduces the search to O(klog⁡k)O(k \log k) rather than exponential, a classical result exploited by CART-style implementations. Many practical libraries instead simplify by one-hot encoding categories, trading some split expressiveness for implementation simplicity.

Why does an unconstrained tree tend to overfit rather than just fit well?

Because the greedy split search always finds some split that strictly reduces training impurity as long as any variation in the target remains, the recursion can continue until every leaf contains a single training example, driving training error to exactly zero. At that point the tree has stopped modeling the underlying signal and started encoding sampling noise and idiosyncrasies specific to the training set. Because those idiosyncrasies do not generalize, held-out accuracy is typically far worse than training accuracy for a fully grown tree — the classic bias-variance tradeoff manifesting as a variance problem.

How is regression handled differently from classification within the same algorithm?

The overall recursive-partitioning skeleton is identical; only two functions change. The impurity criterion becomes the within-node variance (or sum of squared residuals) of the continuous target, rather than Gini or entropy over class proportions. The leaf prediction becomes the mean of the target values in that region rather than the majority class or class-probability vector. Everything else — the split search over features and thresholds, the stopping conditions, the recursive structure — is shared code, which is why CART unified both tasks under one framework rather than treating them as separate algorithms.

Can a decision tree use the same feature more than once along a single root-to-leaf path?

Yes, and this is common and expected. A feature like "income" might be split at 50k50k near the root and then split again at 80k80k further down a branch, because the optimal threshold for separating classes can differ across different subregions of the data. This ability to re-split on the same feature at different thresholds in different branches is part of what lets trees model non-linear, non-monotonic relationships and feature interactions without any explicit feature engineering.

What is the relationship between a single decision tree and methods like random forests or gradient boosting?

Single decision trees are the base learner inside both families of ensemble methods, but they are grown and combined differently. Random forests grow many deep, high-variance trees independently on bootstrapped samples and average their predictions to cancel out variance, a technique built on Bagging and the Bootstrap. Gradient boosting instead grows shallow, high-bias trees sequentially, where each new tree is fit to the residual errors of the ensemble so far. Both approaches exist precisely because a single tree, while interpretable, is usually not competitive in raw predictive accuracy against a well-tuned ensemble.

Why can two different runs of the same tree-growing algorithm on similar data produce very different trees?

Decision trees are notoriously high-variance estimators: because splits are chosen greedily, a tiny change in the training data — even removing or adding a handful of points — can flip which candidate threshold has the highest impurity gain at the root, and once the root split changes, every subsequent recursive call operates on entirely different subsets, cascading differences throughout the tree. This instability is a direct consequence of the greedy, non-backtracking search rather than a bug, and it is the primary statistical motivation for averaging many trees in ensemble methods rather than relying on one.

Where this leads