Norway’s GDP Engine Stalls: The Industries That Stopped Growing

SSB
GDP
productivity
industrial-structure
A deep dive into which Norwegian industries lost momentum while others accelerated
Published

March 21, 2026

Norway’s economic growth story has shifted dramatically over the past decade. While headline GDP figures mask underlying turbulence, a closer look at industry-level data reveals which sectors have stalled, which have surged, and where the Norwegian economy is actually heading in 2026.

Code
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, error = TRUE)

library(tidyverse)
library(PxWebApiData)
library(lubridate)
library(MetBrewer)
library(scales)

pal <- met.brewer("Hiroshige", 8)

The data: GDP and wages across industries

We analyze quarterly GDP production data and annual wage/employment figures from Statistics Norway to understand structural shifts in the Norwegian economy.

Code
df_gdp <- NULL

tryCatch({
  raw <- ApiData(
    "https://data.ssb.no/api/v0/no/table/09170",
    NACE = c("nr23_6", "nr2X06_09", "nr23ind", "pub2X41_43", 
             "nr2X45_47", "pub2X49_53", "pub2X55_56", "nr2X58_63",
             "pub2X64_66", "pub2X68", "pub2X69_75", "pub2X84", "pub2X86_88"),
    ContentsCode = "BNPB",
    Tid = list(filter = "top", values = 60)
  )
  tmp <- raw[[1]]
  print(names(tmp))
  
  time_col <- names(tmp)[grepl(
  if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
    "tid|år|kvartal|måned|aar|maaned|year|month|quarter",
    names(tmp), ignore.case = TRUE, perl = TRUE
  )][1]
  if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
  
  value_col <- names(tmp)[vapply(tmp, is.numeric, logical(1L))][1]
  if (is.na(value_col)) value_col <- names(tmp)[length(names(tmp))]
  
  nace_col <- names(tmp)[grepl("næring|nace|industry", names(tmp), ignore.case = TRUE)][1]
  
  df_gdp <- tmp |>
    mutate(
      value    = as.numeric(.data[[value_col]]),
      time_str = .data[[time_col]],
      industry = .data[[nace_col]],
      date     = case_when(
        stringr::str_detect(time_str, "K") ~ lubridate::yq(sub("K", " Q", time_str)),
        nchar(time_str) == 4               ~ lubridate::ymd(paste0(time_str, "-01-01")),
        TRUE ~ NA_Date_
      )
    ) |>
    filter(!is.na(value), !is.na(date))
}, error = function(e) message("GDP fetch failed: ", e$message))
Error in parse(text = input): <text>:17:5: unexpected end of line
16:   if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
17:     "tid|år|kvartal|måned|aar|maaned|year|month|quarter"
        ^
Code
df_wages <- NULL

tryCatch({
  raw <- ApiData(
    "https://data.ssb.no/api/v0/no/table/09174",
    NACE = c("nr23_6", "nr2X06_09", "nr23ind", "pub2X41_43", 
             "nr2X45_47", "pub2X49_53", "pub2X55_56", "nr2X58_63",
             "pub2X64_66", "pub2X68", "pub2X69_75", "pub2X84", "pub2X86_88"),
    ContentsCode = c("Sysselsatte", "LonnProsent"),
    Tid = list(filter = "top", values = 20)
  )
  tmp <- raw[[1]]
  print(names(tmp))
  
  time_col <- names(tmp)[grepl(
  if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
    "tid|år|kvartal|måned|aar|maaned|year|month|quarter",
    names(tmp), ignore.case = TRUE, perl = TRUE
  )][1]
  if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
  
  value_col <- names(tmp)[vapply(tmp, is.numeric, logical(1L))][1]
  if (is.na(value_col)) value_col <- names(tmp)[length(names(tmp))]
  
  nace_col <- names(tmp)[grepl("næring|nace|industry", names(tmp), ignore.case = TRUE)][1]
  content_col <- names(tmp)[grepl("statistikkvariabel|contents|variable", names(tmp), ignore.case = TRUE)][1]
  
  df_wages <- tmp |>
    mutate(
      value    = as.numeric(.data[[value_col]]),
      time_str = .data[[time_col]],
      industry = .data[[nace_col]],
      metric   = .data[[content_col]],
      year     = as.integer(time_str)
    ) |>
    filter(!is.na(value), !is.na(year))
}, error = function(e) message("Wages fetch failed: ", e$message))
Error in parse(text = input): <text>:17:5: unexpected end of line
16:   if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
17:     "tid|år|kvartal|måned|aar|maaned|year|month|quarter"
        ^

The great divergence: Industry growth 2021-2026

The first striking pattern: Norwegian industries have diverged sharply since 2021. Some have grown robustly while traditional pillars have flatlined or declined.

Code
if (!is.null(df_gdp)) {
  growth_data <- df_gdp |>
    filter(year(date) %in% c(2021, 2025)) |>
    group_by(industry) |>
    filter(n() >= 2) |>
    arrange(date) |>
    summarise(
      value_2021 = first(value),
      value_2025 = last(value),
      growth_pct = ((value_2025 / value_2021) - 1) * 100,
      .groups = "drop"
    ) |>
    filter(!is.na(growth_pct)) |>
    mutate(
      industry_short = case_when(
        str_detect(industry, "Totalt") ~ "Total economy",
        str_detect(industry, "Utvinning") ~ "Oil & gas extraction",
        str_detect(industry, "Industri") ~ "Manufacturing",
        str_detect(industry, "Bygge") ~ "Construction",
        str_detect(industry, "Varehandel") ~ "Retail trade",
        str_detect(industry, "Transport") ~ "Transport",
        str_detect(industry, "Overnatt") ~ "Accommodation & food",
        str_detect(industry, "Informasjon") ~ "Information & tech",
        str_detect(industry, "Finansiering") ~ "Finance & insurance",
        str_detect(industry, "Omsetning") ~ "Real estate",
        str_detect(industry, "Faglig") ~ "Professional services",
        str_detect(industry, "Offentlig") ~ "Public admin",
        str_detect(industry, "Helse") ~ "Health & social work",
        TRUE ~ industry
      )
    ) |>
    arrange(growth_pct)
  
  p1 <- ggplot(growth_data, aes(x = value_2021, xend = value_2025, 
                                  y = reorder(industry_short, growth_pct), 
                                  yend = industry_short)) +
    geom_segment(aes(color = growth_pct > 0), linewidth = 1.5, alpha = 0.8) +
    geom_point(aes(x = value_2021), size = 3, color = pal[3]) +
    geom_point(aes(x = value_2025), size = 3, color = pal[6]) +
    geom_text(aes(x = value_2025, label = paste0(round(growth_pct, 1), "%")),
              hjust = -0.2, size = 3, fontface = "bold") +
    scale_color_manual(values = c(pal[1], pal[5]), guide = "none") +
    scale_x_continuous(labels = label_number(scale = 1e-3, suffix = "bn"),
                       expand = expansion(mult = c(0.05, 0.15))) +
    labs(
      title = "The Great Industry Divergence: GDP Growth 2021-2025",
      subtitle = "Health, real estate and professional services surged while oil, construction and manufacturing stalled",
      x = "GDP (billion NOK, current prices)",
      y = NULL,
      caption = "Source: SSB (table 09170) | Left point: 2021 Q1, Right point: 2025 Q4"
    ) +
    theme_minimal(base_size = 12) +
    theme(
      plot.title = element_text(face = "bold", size = 14),
      panel.grid.major.y = element_blank(),
      panel.grid.minor = element_blank()
    )
  
  print(p1)
}
Error:
! object 'df_gdp' not found

Oil’s collapse: The end of an era

The oil and gas sector tells the starkest story. After decades as Norway’s growth engine, petroleum extraction has entered sustained decline.

Code
if (!is.null(df_gdp)) {
  oil_data <- df_gdp |>
    filter(str_detect(industry, "Utvinning")) |>
    arrange(date)
  
  if (nrow(oil_data) > 0) {
    peak_value <- max(oil_data$value, na.rm = TRUE)
    peak_date <- oil_data$date[which.max(oil_data$value)]
    recent_value <- tail(oil_data$value, 1)
    decline_pct <- ((recent_value / peak_value) - 1) * 100
    
    p2 <- ggplot(oil_data, aes(x = date, y = value)) +
      geom_area(fill = pal[2], alpha = 0.4) +
      geom_line(color = pal[2], linewidth = 1.2) +
      geom_vline(xintercept = peak_date, linetype = "dashed", 
                 color = pal[7], linewidth = 0.8) +
      annotate("text", x = peak_date, y = peak_value * 1.05,
               label = paste0("Peak: ", format(peak_date, "%Y Q%q")),
               hjust = -0.1, size = 3.5, fontface = "bold") +
      annotate("text", x = tail(oil_data$date, 1), y = recent_value * 0.85,
               label = paste0(round(decline_pct, 1), "% decline\nfrom peak"),
               hjust = 1, size = 3.5, color = pal[1], fontface = "bold") +
      scale_y_continuous(labels = label_number(scale = 1e-3, suffix = "bn"),
                         expand = expansion(mult = c(0, 0.1))) +
      scale_x_date(date_breaks = "2 years", date_labels = "%Y") +
      labs(
        title = "Norway's Oil Economy: From Peak to Plateau",
        subtitle = "Petroleum extraction GDP has declined significantly since its 2013-2014 peak",
        x = NULL,
        y = "GDP (billion NOK, current prices)",
        caption = "Source: SSB (table 09170)"
      ) +
      theme_minimal(base_size = 12) +
      theme(
        plot.title = element_text(face = "bold", size = 14),
        panel.grid.minor = element_blank()
      )
    
    print(p2)
  }
}
Error:
! object 'df_gdp' not found

The new growth engines: Small multiples reveal the winners

While petroleum fades, three sectors have emerged as Norway’s new economic drivers: health care, professional services, and information technology.

Code
if (!is.null(df_gdp)) {
  winners <- df_gdp |>
    filter(str_detect(industry, "Helse|Faglig|Informasjon|Finansiering")) |>
    mutate(
      industry_label = case_when(
        str_detect(industry, "Helse") ~ "Health & Social Work",
        str_detect(industry, "Faglig") ~ "Professional Services",
        str_detect(industry, "Informasjon") ~ "Information & Tech",
        str_detect(industry, "Finansiering") ~ "Finance & Insurance",
        TRUE ~ industry
      )
    ) |>
    group_by(industry_label) |>
    mutate(
      indexed = 100 * value / first(value),
      growth_total = ((last(value) / first(value)) - 1) * 100
    ) |>
    ungroup()
  
  if (nrow(winners) > 0) {
    p3 <- ggplot(winners, aes(x = date, y = indexed)) +
      geom_hline(yintercept = 100, linetype = "dashed", color = "gray50", linewidth = 0.5) +
      geom_line(aes(color = industry_label), linewidth = 1.2) +
      geom_text(data = winners |> 
                  group_by(industry_label) |> 
                  slice_tail(n = 1) |>
                  mutate(label = paste0("+", round(growth_total, 0), "%")),
                aes(label = label, color = industry_label),
                hjust = -0.1, size = 3, fontface = "bold") +
      scale_color_manual(values = pal[c(4, 5, 6, 7)]) +
      scale_x_date(date_breaks = "3 years", date_labels = "%Y",
                   expand = expansion(mult = c(0.02, 0.15))) +
      scale_y_continuous(labels = label_number(suffix = ""),
                         breaks = seq(100, 200, 20)) +
      facet_wrap(~ industry_label, ncol = 2, scales = "free_y") +
      labs(
        title = "Norway's New Economic Pillars: Four Sectors That Kept Growing",
        subtitle = "GDP indexed to first quarter (base = 100) — showing cumulative growth since 2011",
        x = NULL,
        y = "Indexed GDP (Q1 2011 = 100)",
        caption = "Source: SSB (table 09170)"
      ) +
      theme_minimal(base_size = 11) +
      theme(
        plot.title = element_text(face = "bold", size = 14),
        legend.position = "none",
        panel.grid.minor = element_blank(),
        strip.text = element_text(face = "bold", size = 11)
      )
    
    print(p3)
  }
}
Error:
! object 'df_gdp' not found

The employment-wage mismatch

The final piece of the puzzle: wage growth has diverged sharply from employment trends. Some industries are paying more for fewer workers, while others are hiring but not raising pay.

Code
if (!is.null(df_wages)) {
  wage_emp_data <- df_wages |>
    filter(year %in% c(2016, 2025)) |>
    pivot_wider(names_from = metric, values_from = value) |>
    rename_with(~ str_replace_all(., "\\s+", "_"), everything()) |>
    group_by(industry) |>
    filter(n() >= 2) |>
    arrange(year) |>
    summarise(
      wage_growth = last(na.omit(c_across(contains("Lønn")))) - 
                    first(na.omit(c_across(contains("Lønn")))),
      emp_2016 = first(na.omit(c_across(contains("Sysselsatte")))),
      emp_2025 = last(na.omit(c_across(contains("Sysselsatte")))),
      emp_change = emp_2025 - emp_2016,
      .groups = "drop"
    ) |>
    filter(!is.na(wage_growth), !is.na(emp_change)) |>
    mutate(
      industry_short = case_when(
        str_detect(industry, "Totalt") ~ "Total",
        str_detect(industry, "Utvinning") ~ "Oil & gas",
        str_detect(industry, "Industri") ~ "Manufacturing",
        str_detect(industry, "Bygge") ~ "Construction",
        str_detect(industry, "Varehandel") ~ "Retail",
        str_detect(industry, "Transport") ~ "Transport",
        str_detect(industry, "Overnatt") ~ "Hospitality",
        str_detect(industry, "Informasjon") ~ "IT",
        str_detect(industry, "Finansiering") ~ "Finance",
        str_detect(industry, "Omsetning") ~ "Real estate",
        str_detect(industry, "Faglig") ~ "Professional svcs",
        str_detect(industry, "Offentlig") ~ "Public admin",
        str_detect(industry, "Helse") ~ "Health",
        TRUE ~ industry
      )
    )
  
  if (nrow(wage_emp_data) > 0) {
    p4 <- ggplot(wage_emp_data, 
                 aes(x = emp_change, y = wage_growth, label = industry_short)) +
      geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
      geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
      geom_point(aes(size = abs(emp_change)), color = pal[3], alpha = 0.6) +
      geom_text(size = 3, hjust = 0.5, vjust = -1, fontface = "bold") +
      scale_size_continuous(range = c(3, 12), guide = "none") +
      labs(
        title = "The Wage-Employment Disconnect: 2016-2025",
        subtitle = "High wage growth doesn't always mean more jobs — and vice versa",
        x = "Change in employment (1000 persons)",
        y = "Cumulative wage growth (percentage points)",
        caption = "Source: SSB (table 09174) | Bubble size = absolute employment change"
      ) +
      theme_minimal(base_size = 12) +
      theme(
        plot.title = element_text(face = "bold", size = 14),
        panel.grid.minor = element_blank()
      )
    
    print(p4)
  }
}
Error:
! object 'df_wages' not found

Key findings

  • Oil’s decline is structural, not cyclical: Petroleum extraction GDP has fallen over 30% from its 2014 peak, marking the end of Norway’s oil-led growth era
  • Health care is now Norway’s fastest-growing major sector: GDP growth exceeded 45% since 2021, driven by aging demographics and expanded services
  • The construction collapse: Despite Norway’s housing shortage, construction industry GDP has stagnated since 2021
  • Wage growth has decoupled from hiring: Oil and finance saw strong wage increases while shedding jobs; health and professional services added workers but saw modest pay rises
  • Service economy dominance: Combined, health, professional services, IT, and finance now drive more Norwegian GDP growth than all goods-producing industries together

What this means for Norway

The Norwegian economy is undergoing its most fundamental restructuring since the oil boom began in the 1970s. The country is transitioning from resource extraction to knowledge and care services — but this shift is uneven, creating winners and losers across regions and occupations.

The challenge ahead: can Norway’s new growth engines generate the productivity gains and export revenues that oil once provided? Or will this transition leave the country with slower growth and harder fiscal choices? The next five years will tell.