Back to Blog
Python

GridSearchCV vs RandomizedSearchCV in Scikit-Learn: How to Choose

Compare GridSearchCV and RandomizedSearchCV in scikit-learn: how each searches the parameter space, runtime tradeoffs, and when to use one or the other.

scikit-learnhyperparameter tuningGridSearchCVRandomizedSearchCVcross-validationmodel selection
A visual comparison of an exhaustive grid search and a random sampling search over a two-dimensional hyperparameter space in scikit-learn.

When you need to tune hyperparameters in scikit-learn, the two standard tools are GridSearchCV and RandomizedSearchCV. GridSearchCV exhaustively evaluates every combination in a defined parameter grid, while RandomizedSearchCV samples a fixed number of combinations from parameter values, which can be supplied as lists or SciPy distributions. The choice between them affects runtime, coverage, and how you define the search space.

The examples in this article assume you already have training data in X_train and y_train.

The Core Difference Between Exhaustive and Sampled Search

GridSearchCV builds a Cartesian product of all parameter values you provide and evaluates every combination using cross-validation. If you specify 3 values for n_estimators, 4 values for max_depth, and 2 values for min_samples_split, it evaluates 3 × 4 × 2 = 24 combinations, each with the configured number of folds.

RandomizedSearchCV instead samples n_iter combinations from distributions or lists you provide. It does not evaluate the full grid. With n_iter=50, it evaluates exactly 50 combinations regardless of how large the underlying parameter space is.

This is the fundamental distinction: exhaustive enumeration versus random sampling with a fixed budget.

How GridSearchCV Evaluates the Parameter Grid

from sklearn.model_selection import GridSearchCV from sklearn.ensemble import RandomForestClassifier param_grid = { "n_estimators": [50, 100, 200], "max_depth": [None, 10, 20], "min_samples_split": [2, 5], } grid_search = GridSearchCV( estimator=RandomForestClassifier(random_state=42), param_grid=param_grid, cv=5, scoring="f1_macro", n_jobs=-1, ) grid_search.fit(X_train, y_train) print(grid_search.best_params_)

GridSearchCV takes a dict where each key maps to a list of candidate values. It constructs the full Cartesian product internally. The number of cross-validation fits is the product of list lengths multiplied by the number of folds. In this example: 3 × 3 × 2 = 18 combinations, times 5 folds, which is 90 cross-validation fits. By default, after the search it also refits the best estimator once on the full training set (refit=True).

The fitted object exposes best_params_, best_score_, and cv_results_, so you can inspect the full evaluation table after the search completes.

How RandomizedSearchCV Samples Parameter Space

from sklearn.model_selection import RandomizedSearchCV from scipy.stats import randint param_distributions = { "n_estimators": randint(50, 300), "max_depth": [None, 10, 20, 30], "min_samples_split": randint(2, 10), } random_search = RandomizedSearchCV( estimator=RandomForestClassifier(random_state=42), param_distributions=param_distributions, n_iter=40, cv=5, scoring="f1_macro", n_jobs=-1, random_state=42, ) random_search.fit(X_train, y_train) print(random_search.best_params_)

The parameter specification differs from GridSearchCV. GridSearchCV expects param_grid with lists. RandomizedSearchCV accepts param_distributions, where each key can be a list or a SciPy distribution such as randint or uniform. Use randint for integer-valued parameters and uniform for float-valued parameters. When you pass a list, RandomizedSearchCV samples from it uniformly with replacement. When you pass a distribution, it draws random values from that distribution.

The number of cross-validation fits is exactly n_iter × cv. With n_iter=40 and cv=5, that is 200 cross-validation fits, plus the default final refit. This count is independent of how many distinct values the distributions could produce.

GridSearchCV vs RandomizedSearchCV: Key Tradeoffs

AspectGridSearchCVRandomizedSearchCV
Search strategyExhaustive Cartesian productRandom sampling with fixed budget
Parameter specparam_grid with listsparam_distributions with lists or distributions
Number of fitsproduct of list lengths × cvn_iter × cv
CoverageComplete within defined gridPartial, probabilistic
Best forSmall grids, known good rangesLarge or continuous spaces
Runtime controlGrows with grid sizeFixed by n_iter

Choosing Between GridSearchCV and RandomizedSearchCV

Use GridSearchCV when the parameter space is small and you already know reasonable ranges. Exhaustive evaluation gives you a complete picture of how each combination performs, which is useful when you want to compare all candidates or when the grid is small enough that runtime is not a concern.

Use RandomizedSearchCV when the parameter space is large, includes continuous values, or when you have a fixed compute budget. With a fixed n_iter, runtime is predictable. Random sampling also tends to explore more distinct values per parameter when distributions are used, because each draw can produce a new value rather than reusing a small set of discrete options.

A practical rule: if the total number of grid combinations times the number of folds runs in acceptable time, GridSearchCV is the simpler choice. Otherwise, RandomizedSearchCV with a reasonable n_iter gives you more coverage per unit of compute.

Computational Cost and Runtime Behavior

The dominant cost in both approaches is model fitting during cross-validation. GridSearchCV's total cross-validation fit count is the product of every parameter-list length and the fold count. For example, if you double the number of candidate values for one parameter, the total fit count doubles.

RandomizedSearchCV decouples the number of cross-validation fits from the size of the parameter space. n_iter controls the number of combinations evaluated, so for a fixed number of folds, doubling n_iter doubles the number of fits. This makes it the preferred option when you have a fixed time or compute budget.

Both classes accept n_jobs and verbose. Setting n_jobs=-1 parallelizes fits across available CPU cores, which helps but does not change the total work. With large models or large datasets, the per-fit time dominates, so reducing the number of fits matters more than parallelization.

Common Pitfalls and Edge Cases

Pitfall 1: Using a distribution where a list is expected, or vice versa. GridSearchCV requires lists; passing a SciPy distribution raises an error. RandomizedSearchCV accepts both, but a list means sampling with replacement, which can evaluate the same value multiple times.

Pitfall 2: Forgetting to set random_state on RandomizedSearchCV. Without it, results are not reproducible. Set both the estimator's random_state and the search's random_state.

Pitfall 3: Ignoring the scoring metric. Both classes default to the estimator's default scoring. For classification, accuracy may not be appropriate for imbalanced data. Pass an explicit scoring argument such as "f1_macro" or a custom scorer.

Pitfall 4: Using the same data for tuning and final evaluation. The best_params_ found by either search are selected based on cross-validation scores. Evaluating the final model on the same data leaks information. Hold out a test set before tuning.

Pitfall 5: For GridSearchCV, the grid size explodes quickly. With 5 parameters each having 5 values, you get 3125 combinations. At 5 folds, that is 15,625 cross-validation fits. Estimate the total fit count before launching a long-running search.

Combining Both Approaches in a Tuning Pipeline

A common production pattern is to use RandomizedSearchCV first to explore a wide space, then use GridSearchCV around the promising region to refine. This combines the exploration efficiency of random sampling with the exhaustive precision of grid search on a narrowed range.

# Stage 1: broad exploration random_search.fit(X_train, y_train) best = random_search.best_params_ # Stage 2: refine around the best values for numeric parameters refined_grid = { "n_estimators": [ max(10, best["n_estimators"] - 20), best["n_estimators"], best["n_estimators"] + 20, ], "min_samples_split": [ max(2, best["min_samples_split"] - 1), best["min_samples_split"], best["min_samples_split"] + 1, ], } grid_refine = GridSearchCV( estimator=RandomForestClassifier(random_state=42), param_grid=refined_grid, cv=5, scoring="f1_macro", n_jobs=-1, ) grid_refine.fit(X_train, y_train) print(grid_refine.best_params_)

This two-stage approach keeps the total fit count manageable while still performing a dense search near the most promising region. It is especially useful when the initial parameter space is large and the best region is unknown. Non-numeric parameters need separate handling when constructing the refined grid; for example, if the best max_depth is numeric, choose neighboring values manually, and if it is None, keep it as a candidate value.

GridSearchCV vs RandomizedSearchCV in Scikit-Learn: When to Use Each | RYUSLOG DEV