Skip to content

Time Series Forecasting: Mastering Trends, Seasonality, and Stationarity

DS
LDS Team
Let's Data Science
13 minAudio
Listen Along
0:00/ 0:00
AI voice

Every January, an airline sees a dip in bookings. Every July, passengers flood the terminals. The pattern repeats year after year, but the overall numbers keep climbing. If you tried to predict next month's passenger count using the same techniques you'd use to predict house prices from square footage, you'd get terrible results. The reason: time series data carries information in its order, and that order is everything.

Time series analysis is the discipline of extracting meaningful patterns from data points collected at regular time intervals. Whether you're forecasting energy demand, monitoring server latency, or predicting retail sales, the core techniques remain the same: decompose the signal into its constituent parts, stabilize the statistical properties, and then model what's left. This article walks through every fundamental concept you need, using monthly airline passenger data as a running example from start to finish.

Temporal Dependence Sets Time Series Apart

Time series data is defined by temporal dependence, meaning the value at any given time step is conditioned on previous observations. Standard machine learning assumes that data points are independent and identically distributed (IID). Time series data breaks this assumption because the ordering of observations carries the predictive signal.

Think of it this way: if you shuffle a dataset of cat and dog images, each image retains its label. A cat photo is still a cat. But if you shuffle a series of monthly passenger counts, you destroy the trend, erase the seasonal pattern, and turn the data into meaningless noise.

This has practical consequences. Algorithms like random forests or gradient boosting treat rows as exchangeable by default. Apply them to raw time series without engineering lag features, rolling windows, or date-based splits, and you'll overfit to the row index rather than learning the actual temporal structure.

Key Insight: The single biggest conceptual shift from tabular ML to time series work is accepting that row order is not arbitrary. It contains the signal.

The Autocorrelation Problem

In ordinary linear regression, we assume errors (residuals) are independent. In time series, residuals often correlate with their own past values. This is called autocorrelation, and it means a positive error today makes a positive error tomorrow more likely.

Ignoring autocorrelation leads to understated standard errors, overconfident predictions, and models that look great on training data but fall apart in production. We'll cover how to detect it using ACF and PACF plots later in this article.

Components of a Time Series

A time series is rarely just a single smooth line. It's a composite of distinct forces layered on top of each other. Decomposing a series into these forces lets you analyze and model each one separately.

Time series components decomposition showing trend, seasonality, and residuals for airline passenger dataClick to expandTime series components decomposition showing trend, seasonality, and residuals for airline passenger data

The three core components are:

ComponentSymbolWhat It CapturesAirline Passenger Example
TrendTtT_tLong-term direction (growth or decline)Passenger counts rising from ~100 to ~500 over 12 years
SeasonalityStS_tRepeating pattern over a fixed periodSummer peaks every July, winter troughs every February
ResidualsRtR_tRandom noise after removing trend and seasonalityUnexpected spikes from one-off events (strikes, weather)

Additive and Multiplicative Decomposition

The question is how these components combine. There are two models:

Additive Model assumes the seasonal fluctuation stays the same absolute size regardless of the trend level:

Yt=Tt+St+RtY_t = T_t + S_t + R_t

Where:

  • YtY_t is the observed value at time tt
  • TtT_t is the trend component at time tt
  • StS_t is the seasonal component at time tt
  • RtR_t is the residual (noise) at time tt

Multiplicative Model assumes the seasonal fluctuation scales proportionally with the trend:

Yt=Tt×St×RtY_t = T_t \times S_t \times R_t

Where:

  • YtY_t is the observed value at time tt
  • TtT_t is the trend component (same as above)
  • StS_t is the seasonal index (centered around 1.0; a value of 1.15 means 15% above trend)
  • RtR_t is the multiplicative residual (centered around 1.0)

In Plain English: The additive model says "every July, we get 50 extra passengers." The multiplicative model says "every July, we get 28% more passengers than the trend." Look at your raw plot: if the peaks and troughs get wider as the series climbs, that's multiplicative. If they stay the same height, that's additive. For our airline data, the summer peaks clearly grow over the years, so we'll use multiplicative decomposition.

Decomposing the Airline Passenger Series

The seasonal_decompose function from statsmodels (v0.14.6 as of March 2026) handles both additive and multiplicative decomposition. Let's apply it to our running example.

Expected output:

text
Shape: (144, 1)

First 5 rows:
            passengers
month
1949-01-01          99
1949-02-01          96
1949-03-01         116
1949-04-01         119
1949-05-01         120

Range: 96 to 603

We've created 12 years (144 months) of synthetic passenger data with a clear upward trend and multiplicative seasonality. Now let's decompose it.

Expected output: Four vertically stacked plots. The Observed panel shows the raw data with widening peaks. The Trend panel shows a smooth upward line from ~115 to ~475. The Seasonal panel shows a repeating 12-month cycle with peaks around 1.28 (July/August) and troughs around 0.82 (February/November). The Residuals panel shows random scatter around 1.0 with no visible pattern, confirming the multiplicative model fits well.

Pro Tip: If your residuals still show a repeating pattern after decomposition, you likely chose the wrong model type. Switch between additive and multiplicative, or consider applying a log transform before additive decomposition (since log(T×S×R)=logT+logS+logR\log(T \times S \times R) = \log T + \log S + \log R, log-transforming a multiplicative series turns it into an additive one).

Stationarity: The Foundation of Time Series Modeling

Stationarity means that the statistical properties of a time series remain constant over time. Specifically, its mean, variance, and autocovariance do not depend on when you measure them. Most classical forecasting methods (ARIMA, exponential smoothing, VAR models) assume stationarity, either explicitly or after differencing.

The intuition is straightforward: if the "rules" generating the data keep changing, a model trained on past data can't generalize to the future. A series averaging 100 passengers in 1949 but 500 in 1960 has a shifting mean. A model that assumes the mean is constant will produce wildly wrong predictions.

Formal Definition (Weak Stationarity)

A time series YtY_t is weakly stationary if:

E[Yt]=μfor all tE[Y_t] = \mu \quad \text{for all } t

Var(Yt)=σ2for all t\text{Var}(Y_t) = \sigma^2 \quad \text{for all } t

Cov(Yt,Yt+k)=γ(k)depends only on lag k, not on t\text{Cov}(Y_t, Y_{t+k}) = \gamma(k) \quad \text{depends only on lag } k, \text{ not on } t

Where:

  • E[Yt]E[Y_t] is the expected value (mean) of the series at time tt
  • μ\mu is a constant mean that does not change over time
  • σ2\sigma^2 is a constant variance
  • γ(k)\gamma(k) is the autocovariance function, depending only on the lag kk

In Plain English: For our airline data, stationarity would require that the average monthly passengers stay the same whether you measure in 1949 or 1960 (they don't, the mean grows). It would also require that the month-to-month variability stays constant (it doesn't, the swings get bigger). And the relationship between this month and next month should be the same in 1950 as in 1958. Our raw passenger series violates all three conditions.

Visual Stationarity Check with Rolling Statistics

Before running formal tests, a quick visual check is to plot rolling mean and rolling standard deviation. If either drifts upward or downward, the series is not stationary.

Expected output: Two plots. The top plot shows the raw passenger data in gray with a steadily climbing black rolling mean line. The bottom plot shows the rolling standard deviation also trending upward. Both confirm non-stationarity: the mean drifts from ~120 to ~460, and the standard deviation grows from ~20 to ~80.

Formal Stationarity Tests: ADF and KPSS

Visual checks are useful but subjective. Statistical tests give you a definitive yes-or-no answer (with a p-value to quantify confidence).

Stationarity testing decision flowchart showing how to combine ADF and KPSS test resultsClick to expandStationarity testing decision flowchart showing how to combine ADF and KPSS test results

The Augmented Dickey-Fuller Test

The Augmented Dickey-Fuller (ADF) test is the most widely used stationarity test, available in statsmodels.tsa.stattools.adfuller. It tests for the presence of a unit root, which indicates non-stationarity.

  • Null hypothesis (H0H_0): The series has a unit root (non-stationary)
  • Alternative hypothesis (H1H_1): The series is stationary

If the p-value is below 0.05, reject H0H_0 and conclude stationarity. The ADF test extends the original Dickey-Fuller test by including lagged difference terms to handle higher-order autocorrelation (see the hypothesis testing guide for more on null hypotheses and p-values).

The KPSS Test: A Complementary Check

The KPSS (Kwiatkowski-Phillips-Schmidt-Shin) test flips the hypotheses:

  • Null hypothesis (H0H_0): The series is stationary
  • Alternative hypothesis (H1H_1): The series has a unit root (non-stationary)

Running both tests together resolves ambiguous cases:

ADF ResultKPSS ResultInterpretation
Stationary (p < 0.05)Stationary (p > 0.05)Confirmed stationary
Non-stationary (p > 0.05)Non-stationary (p < 0.05)Confirmed non-stationary
StationaryNon-stationaryTrend-stationary (needs differencing)
Non-stationaryStationaryInconclusive (possible structural break)

Pro Tip: Always run ADF and KPSS together. Relying on a single test can mislead you, especially with borderline cases or structural breaks in the data. This dual-test approach is recommended by both the statsmodels documentation and recent time series best practices.

Testing Our Airline Passenger Series

Expected output:

text
=== Raw Passengers ===
ADF Statistic: 0.1640, p-value: 0.9702
KPSS Statistic: 1.6672, p-value: 0.0100
Verdict: NON-STATIONARY (both tests agree)

Both tests agree: the raw passenger series is decisively non-stationary. The ADF p-value of 0.97 is nowhere near the 0.05 threshold, and the KPSS test rejects stationarity with a p-value of 0.01.

Transforming Non-Stationary Data

When a series is non-stationary, we transform it. The two main weapons are differencing (to remove trend) and log transformation (to stabilize variance).

Differencing

Differencing replaces each value with the change from the previous time step:

ΔYt=YtYt1\Delta Y_t = Y_t - Y_{t-1}

Where:

  • ΔYt\Delta Y_t is the first difference at time tt
  • YtY_t is the observed value at time tt
  • Yt1Y_{t-1} is the observed value at the previous time step

In Plain English: Instead of predicting that there will be 450 passengers this month, we predict the change: "30 more passengers than last month." The change fluctuates around zero (no trend), which is much easier to model. For our airline data, the raw series keeps climbing, but the month-over-month changes hover around a roughly constant level.

If the series has both a trend and seasonality, you may need seasonal differencing (subtracting the value from 12 months ago) in addition to first differencing:

Δ12Yt=YtYt12\Delta_{12} Y_t = Y_t - Y_{t-12}

The number of times you difference is called the order of differencing (dd in ARIMA notation). Overdifferencing introduces artificial patterns, so the principle is: difference only as many times as needed to achieve stationarity.

Log Transformation for Changing Variance

When the variance grows with the level (heteroscedasticity), a log transform can stabilize it before differencing:

Zt=log(Yt)Z_t = \log(Y_t)

This converts a multiplicative relationship into an additive one. For our airline data with widening seasonal swings, logging first and then differencing is standard practice.

Making Our Passenger Data Stationary

Expected output:

text
First-Differenced: ADF p=0.0000, KPSS p=0.1000 -> STATIONARY

Original range: 96 to 603
After differencing: -109 to 103 (centered near 2.3)

First differencing alone achieves stationarity for this series. The ADF test confirms stationarity with a p-value of essentially zero, and the KPSS test agrees. The transformed data now fluctuates around a mean near zero instead of climbing from 96 to 603.

Common Pitfall: Don't blindly apply double differencing. Over-differencing creates artificial negative autocorrelation (each value becomes negatively correlated with its neighbors), making downstream models perform worse. Always check stationarity after each round and stop as soon as both tests agree.

ACF and PACF: Reading the Memory of a Series

Once your data is stationary, the next step is understanding its autocorrelation structure: how strongly does the series at time tt relate to its own past values? Two plots answer this: the Autocorrelation Function (ACF) and the Partial Autocorrelation Function (PACF).

ACF and PACF interpretation guide showing common patterns and their model implicationsClick to expandACF and PACF interpretation guide showing common patterns and their model implications

Autocorrelation Function (ACF)

The ACF measures the correlation between YtY_t and YtkY_{t-k} for each lag kk:

rk=t=k+1T(YtYˉ)(YtkYˉ)t=1T(YtYˉ)2r_k = \frac{\sum_{t=k+1}^{T}(Y_t - \bar{Y})(Y_{t-k} - \bar{Y})}{\sum_{t=1}^{T}(Y_t - \bar{Y})^2}

Where:

  • rkr_k is the autocorrelation at lag kk
  • YtY_t is the value of the series at time tt
  • Yˉ\bar{Y} is the overall mean of the series
  • TT is the total number of observations

In Plain English: ACF asks, "how similar is today's passenger count to the count from kk months ago?" A high ACF at lag 12 means this July's count strongly resembles last July's. But ACF captures both direct and indirect influence. If January affects February, and February affects March, ACF will show a correlation between January and March even if there's no direct link.

Partial Autocorrelation Function (PACF)

PACF strips away intermediate effects. It measures the direct correlation between YtY_t and YtkY_{t-k} after removing the influence of all lags in between (lags $1, 2, \ldots, k-1$).

Think of it as the "telephone game" analogy. Alice tells Bob a rumor, Bob tells Charlie. ACF measures the similarity between Alice's version and Charlie's (high, because the chain is short). PACF asks: did Alice tell Charlie directly? If not, the partial correlation between Alice and Charlie drops to near zero.

How to Read the Plots

The interpretation rules for ACF and PACF directly determine the parameters for ARIMA models:

PatternACF BehaviorPACF BehaviorSuggested Model
AR(pp) processGradual exponential decaySharp cutoff after lag ppAutoRegressive order pp
MA(qq) processSharp cutoff after lag qqGradual decayMoving Average order qq
ARMA(p,qp,q)Gradual decayGradual decayMixed model
Seasonal patternSpikes at seasonal lags (12, 24, 36)Spike at lag 12Add seasonal component

These interpretation rules come from the Box-Jenkins methodology (originally published in 1970 and still the standard reference for ARIMA model identification in 2026).

ACF and PACF of Our Differenced Passenger Data

Expected Output:

code
Key observations:
- ACF shows significant spikes at lags 12, 24, 36 (annual seasonality)
- PACF shows a sharp spike at lag 12
- This pattern suggests seasonal differencing (D=1) at period 12 is needed

Expected output: Two plots stacked vertically. The ACF plot shows significant spikes at lags 12, 24, and 36, with the blue confidence band marking the 95% significance threshold. The gradual decay at seasonal lags reveals the annual seasonal pattern that first differencing alone doesn't remove. The PACF plot shows a strong spike at lag 12, with most other lags within the confidence band. The console output summarizes these observations.

Key Insight: When you see repeating spikes at multiples of 12 in the ACF, that's the seasonal autocorrelation speaking. First differencing removed the trend, but the seasonality remains. A SARIMA model (the seasonal extension of ARIMA) would handle this by applying seasonal differencing at lag 12 in addition to the first differencing we already did.

Time Series Train/Test Splitting

Splitting time series data for model validation requires respecting the temporal order. This is one of the most common mistakes practitioners make, and it's worth understanding deeply.

Look-Ahead Bias

Standard train_test_split with shuffle=True randomly mixes past and future data points. Your model might train on December 1959 and test on January 1955. In a real forecasting scenario, you can't see the future. Training on future data and testing on past data produces artificially inflated accuracy that evaporates the moment you deploy.

Chronological Split

The simplest correct approach is a time-ordered split: everything before a cutoff date goes to training, everything after goes to testing.

Expected output:

text
Total observations: 144
Training set: 115 months (1949-01 to 1958-07)
Test set:     29 months (1958-08 to 1960-12)

TimeSeriesSplit for Cross-Validation

A single train/test split can be unstable. scikit-learn's TimeSeriesSplit creates expanding training windows, giving you multiple evaluation folds while always respecting temporal order:

Expected output:

text
Fold 1: Train 1949-01 to 1950-12 (24 obs) | Test 1951-01 to 1952-12 (24 obs)
Fold 2: Train 1949-01 to 1952-12 (48 obs) | Test 1953-01 to 1954-12 (24 obs)
Fold 3: Train 1949-01 to 1954-12 (72 obs) | Test 1955-01 to 1956-12 (24 obs)
Fold 4: Train 1949-01 to 1956-12 (96 obs) | Test 1957-01 to 1958-12 (24 obs)
Fold 5: Train 1949-01 to 1958-12 (120 obs) | Test 1959-01 to 1960-12 (24 obs)

Notice how each fold's training set expands forward in time. Fold 1 trains on 2 years and tests on the next 2. Fold 5 trains on 10 years and tests on the final 2. The test set always comes after the training set.

Common Pitfall: Even TimeSeriesSplit can mislead you if your series has regime changes (e.g., a pandemic in the middle). Each fold assumes the future behaves like the past. If the data-generating process changes fundamentally, no time-based split will save you; you need domain-specific judgment about which historical period is relevant.

When to Apply Each Technique

Not every time series problem requires every tool we've covered. Here's a decision framework:

SituationTechniqueWhy
Visual inspection of raw dataPlot the series + rolling mean/stdCatches obvious trend, seasonality, variance changes before any test
Need to separate trend from seasonalityseasonal_decompose (additive or multiplicative)Isolates components for individual analysis or feature engineering
Checking if data is ready for ARIMA/ETSADF + KPSS dual testFormal stationarity confirmation with complementary hypotheses
Series has upward/downward trendFirst differencing (d=1d = 1)Removes trend, stabilizes the mean
Seasonal amplitude grows with levelLog transform before differencingStabilizes variance, converts multiplicative to additive
Seasonal pattern still present after first differencingSeasonal differencing (D=1D = 1, period mm)Removes seasonal autocorrelation
Choosing ARIMA parameters (pp, qq)ACF + PACF plot inspectionPattern of cutoffs and decay suggests appropriate model orders
Model evaluationTimeSeriesSplit, never random shufflePrevents look-ahead bias, gives honest error estimates

When NOT to Use Classical Decomposition

Classical time series decomposition and ARIMA-family models are powerful but not universal. Skip them when:

  • Multiple seasonalities overlap (e.g., hourly data with daily, weekly, and yearly cycles). Use Facebook Prophet or Temporal Fusion Transformers instead.
  • Exogenous variables matter (e.g., weather affecting energy demand). ARIMA's ARIMAX variant exists but is limited; tree-based models or deep learning often handle this better.
  • You have hundreds of related series (e.g., demand for 10,000 SKUs). Global models trained across all series (like N-BEATS or TFT) outperform fitting individual ARIMA models.
  • The series is extremely noisy with no clear structure. No amount of decomposition will help if the signal-to-noise ratio is too low.

Forecasting method selection guide for choosing the right time series approachClick to expandForecasting method selection guide for choosing the right time series approach

Production Considerations

When deploying time series models to production, keep these practical points in mind:

Computational complexity. Fitting ARIMA is O(T×p2)O(T \times p^2) where TT is the series length and pp is the AR order. For most business series (hundreds to low thousands of observations), this runs in milliseconds. For high-frequency data with millions of points, consider downsampling or switching to online learning methods.

Concept drift. The statistical properties you tested for today can change tomorrow. Production systems need automated stationarity monitoring. A scheduled ADF test that fires an alert when p-values shift above a threshold is a simple but effective guard.

Missing data. Real-world series have gaps. Interpolation (linear, spline, or seasonal) before decomposition is usually fine for small gaps (less than 5% of observations). Larger gaps may require imputation models or simply treating the gap as a structural break.

Frequency alignment. Mixing daily, weekly, and monthly data without proper resampling is a common production bug. Always standardize frequencies before analysis. Pandas' resample() method handles this, but be explicit about aggregation (sum vs. mean) to avoid subtle errors.

Conclusion

Time series analysis starts with a different mental model than standard machine learning. The order of observations is the signal, not noise to be shuffled away. Every technique in this article exists to respect and exploit that temporal structure.

Decomposition separates a messy time series into its constituent parts: trend, seasonality, and residuals. Stationarity testing (using ADF and KPSS together) tells you whether the statistical properties are stable enough for classical models. Differencing and log transforms fix the problems when they aren't. ACF and PACF plots reveal the memory structure that determines your model's architecture. And proper time-based splitting prevents the look-ahead bias that makes models look brilliant in notebooks and fail in production.

These fundamentals apply regardless of whether your next step is fitting an ARIMA model, training an LSTM for sequence prediction, or building a multi-step forecasting pipeline. The decomposition, stationarity checks, and autocorrelation analysis you've learned here are the diagnostic tools you'll reach for first, every single time. Master them, and the advanced techniques become far easier to apply correctly.

Frequently Asked Interview Questions

Q: What is stationarity, and why do most time series models require it?

Stationarity means the statistical properties of a series (mean, variance, autocovariance) don't change over time. Models like ARIMA assume stationarity because their parameters are estimated from historical data. If the data-generating process keeps shifting, those parameters become stale and predictions degrade. Making data stationary through differencing ensures the patterns learned from the past still hold in the future.

Q: You run an ADF test and get a p-value of 0.07. Is the series stationary?

At the standard 0.05 significance level, you'd fail to reject the null hypothesis, meaning you can't conclude stationarity. But 0.07 is borderline. The right move is to run a KPSS test as a complementary check. If KPSS confirms non-stationarity, apply differencing. If KPSS says stationary, you may have a trend-stationary series that needs detrending rather than differencing.

Q: Explain the difference between additive and multiplicative seasonal decomposition.

Additive decomposition assumes the seasonal effect is a fixed absolute amount added to the trend (e.g., "+50 passengers every July"). Multiplicative decomposition assumes the seasonal effect scales with the trend level (e.g., "+28% every July"). If you plot the data and the seasonal peaks grow taller as the series rises, that's multiplicative. A log transform converts multiplicative to additive.

Q: How do you use ACF and PACF plots to determine ARIMA parameters?

For the AR order (pp): look at PACF for a sharp cutoff. If PACF drops to insignificant after lag 2, set p=2p = 2. For the MA order (qq): look at ACF for a sharp cutoff. If ACF drops after lag 1, set q=1q = 1. If both decay gradually, you likely need a mixed ARMA model. Significant spikes at seasonal lags (12, 24) indicate a seasonal component requiring SARIMA.

Q: Why can't you use standard k-fold cross-validation on time series data?

Standard k-fold randomly assigns observations to folds, mixing past and future data. A model could train on 2025 data and be tested on 2023 data, which is impossible in a real forecasting scenario. This creates look-ahead bias and artificially inflates performance metrics. Use TimeSeriesSplit instead, which creates expanding training windows that always respect temporal order.

Q: Your model's ADF test shows the data is stationary after first differencing, but the ACF still shows significant spikes at lag 12 and 24. What's happening?

First differencing removed the trend but not the seasonal pattern. The series has seasonal autocorrelation at the annual frequency. You need to apply seasonal differencing (subtracting the value from 12 months prior) on top of the first difference. In ARIMA notation, this means setting D=1D = 1 with period m=12m = 12 in a SARIMA model.

Q: When would you choose a deep learning model (LSTM, Transformer) over ARIMA for time series forecasting?

Deep learning models shine when you have large amounts of data (thousands of observations or hundreds of related series), multiple exogenous features, complex nonlinear patterns, or need to forecast many related series simultaneously (global models). ARIMA is often better for short univariate series (under 500 observations) where interpretability matters and you can't afford the tuning overhead of neural networks. For many business use cases, properly tuned ARIMA still beats poorly tuned deep learning.

Q: What is the difference between a random walk and white noise, and how does each appear in an ACF plot?

White noise has zero autocorrelation at all lags. Its ACF shows no significant spikes beyond lag 0. A random walk (Yt=Yt1+ϵtY_t = Y_{t-1} + \epsilon_t) is non-stationary with a unit root. Its ACF decays very slowly because each value is highly correlated with recent values. First-differencing a random walk produces white noise, since ΔYt=ϵt\Delta Y_t = \epsilon_t. This is why differencing is the standard remedy for unit root non-stationarity.

Hands-On Practice

In this hands-on tutorial, we will bridge the gap between theory and practice by dissecting a classic time series dataset: monthly airline passenger numbers. You will learn to identify the invisible forces of trend and seasonality that drive data over time, moving beyond simple observation to mathematical decomposition. By working with real-world data, you will master the essential preprocessing steps, like stationarity checks and seasonal decomposition, that form the foundation of every solid forecasting model.

Dataset: Monthly Passengers (Time Series) Airline passenger data with clear trend and yearly seasonality over 12 years (144 monthly observations). Perfect for time series decomposition and forecasting.

Experiment with the seasonal decomposition by changing the model from 'multiplicative' to 'additive' and observing how the residuals change; a poor fit will leave a pattern in the residuals. Try adjusting the differencing lag (e.g., shift(12) for seasonal differencing) to see if you can achieve a stronger stationarity result with a lower p-value. Finally, explore how the ACF and PACF plots shift when you apply different transformations, which provides clues for selecting ARIMA parameters.

Practice interview problems based on real data

1,500+ SQL & Python problems across 15 industry datasets — the exact type of data you work with.

Try 250 free problems
Free Career Roadmaps8 PATHS

Step-by-step roadmaps from zero to job-ready — curated courses, salary data, and the exact learning order that gets you hired.

Explore all career paths