Skip to main content
LearnMLAcademy
Algorithms

XGBoost vs Random Forest: Which Should You Choose?

Compare XGBoost and Random Forest, understand how they differ, when to use each model, and how to tune them effectively with practical code examples.

April 28, 202611 min read
xgboostrandom-forestensemblecomparison

Both XGBoost and Random Forest are ensemble methods built on decision trees, and both consistently outperform single models on structured data. But they work in fundamentally different ways — and choosing the wrong one for a given problem can cost you significantly in accuracy, training time, or both.

The Core Difference: Parallel vs Sequential

Random Forest

Builds many trees independently and in parallel. Each tree is trained on a random bootstrap sample of the data, and at each split, only a random subset of features is considered. Final prediction is the average (regression) or majority vote (classification) of all trees.

BaggingParallelLow variance

XGBoost

Builds trees sequentially. Each new tree corrects the residual errors of all previous trees. Uses gradient descent in function space to minimise a regularised objective. Final prediction is the weighted sum of all trees.

BoostingSequentialLow bias

Head-to-Head Comparison

PropertyRandom ForestXGBoost
Accuracy (typical)Very goodExcellent — state-of-the-art on tabular data
Training speedFast (parallelisable)Slower (sequential by nature, but optimised)
Hyperparameter tuningForgiving — few critical paramsComplex — learning rate, depth, subsample, etc.
Overfitting riskLow — hard to overfitHigher — can overfit without regularisation
Handles missing valuesNo (needs imputation)Yes — built-in missing value handling
Feature importanceYes (impurity-based)Yes (gain, cover, weight — more informative)
Memory usageHigh (stores all trees)Lower (smaller trees by design)
Best forQuick baselines, noisy data, wide featuresMaximum accuracy, Kaggle, production systems

Random Forest: Practical Setup

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

model = RandomForestClassifier(
    n_estimators=300,       # more trees = more stable, diminishing returns after ~300
    max_depth=None,         # let trees grow fully (RF is robust to this)
    min_samples_leaf=1,     # default — try 2-5 if overfitting
    max_features='sqrt',    # standard for classification (try 'log2' for wide data)
    n_jobs=-1,              # use all CPU cores
    random_state=42,
    oob_score=True,         # free validation score using out-of-bag samples
)

model.fit(X_train, y_train)
print(f"OOB Score: {model.oob_score_:.4f}")  # reliable val score without a split

# Cross-validation score
cv_scores = cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc')
print(f"CV ROC-AUC: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}")

# Feature importance
import pandas as pd
importances = pd.Series(
    model.feature_importances_,
    index=feature_names
).sort_values(ascending=False)
print(importances.head(10))

XGBoost: Practical Setup with Early Stopping

import xgboost as xgb
from sklearn.model_selection import cross_val_score

model = xgb.XGBClassifier(
    n_estimators=1000,       # high — early stopping will find the right number
    learning_rate=0.05,      # lower = better generalisation, needs more trees
    max_depth=6,             # typical range: 3-8. Deeper = more complex = more overfit risk
    subsample=0.8,           # fraction of rows per tree (reduces overfitting)
    colsample_bytree=0.8,    # fraction of features per tree
    min_child_weight=1,      # min samples in a leaf (increase to reduce overfitting)
    gamma=0,                 # regularisation: minimum loss reduction to split
    reg_alpha=0,             # L1 regularisation (Lasso)
    reg_lambda=1,            # L2 regularisation (Ridge)
    eval_metric='auc',
    use_label_encoder=False,
    random_state=42,
    n_jobs=-1,
)

# Training with early stopping
model.fit(
    X_train, y_train,
    eval_set=[(X_val, y_val)],
    early_stopping_rounds=50,   # stop if val AUC doesn't improve for 50 rounds
    verbose=100
)

print(f"Best iteration: {model.best_iteration}")
print(f"Best val AUC: {model.best_score:.4f}")

Key insight: With XGBoost, always set n_estimators high (500–2000) and rely on early_stopping_rounds to find the optimal number of trees. This prevents overfitting automatically while avoiding the need to grid-search n_estimators.

Hyperparameter Tuning: Optuna (Recommended for Both)

Grid search is too slow for XGBoost's large parameter space. Optuna uses Bayesian optimisation to find good parameters efficiently.

import optuna
from sklearn.model_selection import cross_val_score
import xgboost as xgb

def objective(trial):
    params = {
        'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
        'max_depth': trial.suggest_int('max_depth', 3, 9),
        'subsample': trial.suggest_float('subsample', 0.6, 1.0),
        'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0),
        'reg_alpha': trial.suggest_float('reg_alpha', 1e-8, 1.0, log=True),
        'reg_lambda': trial.suggest_float('reg_lambda', 1e-8, 1.0, log=True),
        'random_state': 42, 'n_jobs': -1,
    }
    model = xgb.XGBClassifier(**params, use_label_encoder=False, eval_metric='logloss')
    score = cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc').mean()
    return score

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100, show_progress_bar=True)
print("Best params:", study.best_params)
print("Best CV AUC:", study.best_value)

Which Should You Choose?

Use Random Forest when…

  • ✓ You need a fast, reliable baseline with minimal tuning
  • ✓ Your dataset is noisy or has many outliers
  • ✓ Interpretability and feature importance matter to stakeholders
  • ✓ You have very wide data (many features, relatively few rows)
  • ✓ Training time is a constraint (RF parallelises fully)

Use XGBoost when…

  • ✓ You need maximum predictive accuracy on structured/tabular data
  • ✓ You are competing (XGBoost won most Kaggle competitions from 2016–2020)
  • ✓ Your dataset has missing values (XGBoost handles them natively)
  • ✓ You have time to tune — the extra effort usually pays off
  • ✓ You are in production and need a smaller model footprint

The Practical Verdict

In practice, start with Random Forest. It is hard to misuse, gives solid results, and takes 10 minutes to set up. If you need more performance and are willing to spend time tuning, switch to XGBoost. On most real-world tabular datasets, a well-tuned XGBoost will beat a well-tuned Random Forest by 1–3% AUC — which matters in production even if it seems small.

When in doubt, run both and let your validation set decide.

Free Download: ML Interview Cheatsheet

100 essential Machine Learning interview questions and answers covering foundations, algorithms, evaluation, deep learning, Python, system design and interview strategy. Enter your email to download it instantly.