SSB data reveals how Norway’s native fertility has collapsed to historic lows while population projections diverge sharply depending on immigration assumptions — a demographic fork in the road with profound consequences.
Published
April 29, 2026
Norway is running a quiet demographic experiment with no guaranteed outcome. Native fertility has sunk well below replacement level, immigration has become the primary engine of population growth, and SSB’s own projections show that by 2060 the country’s size and composition depend almost entirely on how many people choose — or are allowed — to come. The numbers tell a story that goes far beyond statistics.
The Data
Two SSB tables frame this analysis. Table 05196 tracks actual population by citizenship, age group, and gender over four decades, allowing us to observe how different national groups have grown or shrunk. Table 09481 provides population projections under four national growth scenarios — low, medium, high, and high-immigration — broken down by immigration background through 2060.
# ── df1: total population by citizenship, all ages, both sexes ──────────────df1_total <-NULLif (!is.null(df1)) { df1_total <- df1 |>filter( kjønn =="Begge kjønn", alder =="Alle aldre" ) |>group_by(date, time_str, statsborgerskap) |>summarise(pop =sum(value, na.rm =TRUE), .groups ="drop")}# Norwegian vs. foreign citizenship over timedf1_nor_foreign <-NULLif (!is.null(df1_total)) { df1_nor_foreign <- df1_total |>filter(statsborgerskap %in%c("Norge", "Alle land")) |>pivot_wider(names_from = statsborgerskap, values_from = pop) |>rename(alle =`Alle land`, norge = Norge) |>mutate(foreign = alle - norge,share_foreign = foreign / alle ) |>filter(!is.na(alle), alle >0)}# Growth by citizenship groupdf1_growth <-NULLif (!is.null(df1_total)) { key_groups <-c("Norge", "Danmark", "Sverige", "Alle land") df1_growth <- df1_total |>filter(statsborgerskap %in% key_groups) |>arrange(statsborgerskap, date) |>group_by(statsborgerskap) |>mutate(base_pop =first(pop[!is.na(pop)]),index = pop / base_pop *100 ) |>ungroup()}# ── df2: projections – total population under all scenarios ─────────────────df2_total <-NULLif (!is.null(df2)) { sc_labels <-c("Middels nasjonal vekst (Alternativ MMMM)","Lav nasjonal vekst (Alternativ LLML)","Høy nasjonal vekst (Alternativ HHMH)","Høy innvandring (Alternativ MMMH)" ) df2_total <- df2 |>filter( .data[[series_col]] =="Hele befolkningen", kjønn =="Begge kjønn", alder =="Alle aldre", .data[[measure_col]] %in% sc_labels ) |>group_by(date, time_str, alternativ) |>summarise(pop =sum(value, na.rm =TRUE), .groups ="drop") |>mutate(scenario_short =case_when(str_detect(alternativ, "Middels") ~"Medium growth",str_detect(alternativ, "Lav") ~"Low growth",str_detect(alternativ, "Høy nasjonal") ~"High growth",str_detect(alternativ, "innvandring") ~"High immigration",TRUE~ alternativ ) )}# ── df2: immigrant background composition under medium scenario ──────────────df2_comp <-NULLif (!is.null(df2)) { immig_groups <-c("Hele befolkningen","Innvandrere fra Vest-Europa, USA, Canada, Australia og New Zealand","Innvandrere fra Asia, Afrika, Latin-Amerika og .st-Europa utenfor EU" ) df2_comp <- df2 |>filter( .data[[series_col]] %in% immig_groups, kjønn =="Begge kjønn", alder =="Alle aldre", .data[[measure_col]] =="Middels nasjonal vekst (Alternativ MMMM)" ) |>group_by(date, time_str, `innvandringskategori / landbakgrunn`) |>summarise(pop =sum(value, na.rm =TRUE), .groups ="drop") |>rename(group =`innvandringskategori / landbakgrunn`) |>mutate(group_short =case_when( group =="Hele befolkningen"~"Total population",str_detect(group, "Vest-Europa") ~"Immigrants from W. Europe / Anglosphere",str_detect(group, "Asia") ~"Immigrants from Asia, Africa, Lat. Am. & E. Europe (non-EU)",TRUE~ group ) )}# ── Scenario spread: difference between high-immigration and low at each year ─df2_spread <-NULLif (!is.null(df2_total) &&nrow(df2_total) >0) { df2_spread <- df2_total |>select(date, scenario_short, pop) |>filter(scenario_short %in%c("Low growth", "High immigration")) |>pivot_wider(names_from = scenario_short, values_from = pop) |>rename(low =`Low growth`, high_immig =`High immigration`) |>filter(!is.na(low), !is.na(high_immig)) |>mutate(gap_million = (high_immig - low) /1e6)}# ── Foreign share ridge data: age distribution ───────────────────────────────df1_age <-NULLif (!is.null(df1)) { age_years <-c("2004", "2009", "2014", "2019", "2024") df1_age <- df1 |>filter( kjønn =="Begge kjønn", statsborgerskap !="Alle land", time_str %in% age_years, alder !="Alle aldre" ) |>mutate(age_num =suppressWarnings(as.integer(str_extract(alder, "^\\d+"))) ) |>filter(!is.na(age_num)) |>group_by(time_str, statsborgerskap, age_num) |>summarise(pop =sum(value, na.rm =TRUE), .groups ="drop")}
Part 1: The Foreign-Citizen Share Is Rising Steadily
Norway’s population has grown by roughly a third over four decades, but almost none of that growth comes from citizens holding Norwegian passports. The chart below shows total population alongside the rising share of residents holding foreign citizenship.
Code
if (!is.null(df1_nor_foreign) &&nrow(df1_nor_foreign) >0) { pal <-met.brewer("Hiroshige", n =5) p <-ggplot(df1_nor_foreign, aes(x = date)) +geom_area(aes(y = alle /1e6), fill = pal[4], alpha =0.18) +geom_line(aes(y = alle /1e6), colour = pal[4], linewidth =0.8) +geom_area(aes(y = foreign /1e6), fill = pal[1], alpha =0.45) +geom_line(aes(y = foreign /1e6), colour = pal[1], linewidth =1.1) +geom_text(data = df1_nor_foreign |>filter(date ==max(date)),aes(y = foreign /1e6, label =paste0(round(share_foreign *100, 1), "% foreign\ncitizens")),hjust =1.05, vjust =-0.3, size =3.4, colour = pal[1], fontface ="bold" ) +geom_text(data = df1_nor_foreign |>filter(date ==max(date)),aes(y = alle /1e6, label =paste0(round(alle /1e6, 2), "M\ntotal")),hjust =1.05, vjust =1.5, size =3.2, colour = pal[4] ) +scale_x_date(date_breaks ="5 years", date_labels ="%Y") +scale_y_continuous(labels =label_number(suffix ="M")) +labs(title ="Norway's population growth is increasingly driven by foreign citizens",subtitle ="Blue area = residents holding non-Norwegian citizenship; grey area = total population",x =NULL,y ="Population (millions)",caption ="Source: Statistics Norway, table 05196" ) +theme_minimal(base_size =13) +theme(plot.title =element_text(face ="bold", size =14),plot.subtitle =element_text(colour ="grey40", size =11),panel.grid.minor =element_blank() )print(p)}
Part 2: How Different Citizenship Groups Have Grown Since the 1980s
The indexed growth chart below reveals an extraordinary divergence. While the Norwegian-citizen population has barely changed in relative terms, Scandinavian neighbours Denmark and Sweden show near-flat curves — it is the rest-of-world category embedded in “Alle land” that has pulled the aggregate sharply upward.
Code
if (!is.null(df1_growth) &&nrow(df1_growth) >0) { pal2 <-met.brewer("Hiroshige", n =6) group_colours <-c("Alle land"= pal2[2],"Norge"= pal2[5],"Danmark"= pal2[3],"Sverige"= pal2[4] ) p2 <-ggplot(df1_growth, aes(x = date, y = index, colour = statsborgerskap, linewidth = statsborgerskap)) +geom_hline(yintercept =100, linetype ="dashed", colour ="grey60") +geom_line() +scale_linewidth_manual(values =c("Alle land"=1.4, "Norge"=1.1, "Danmark"=0.8, "Sverige"=0.8)) +scale_colour_manual(values = group_colours) +annotate("text", x =as.Date("2005-01-01"), y =101, label ="Baseline (first year = 100)",size =3, colour ="grey50", hjust =0) +scale_x_date(date_breaks ="5 years", date_labels ="%Y") +labs(title ="Residents with Norwegian citizenship barely grew; the rest pulled the total up",subtitle ="Index: population in earliest available year = 100",x =NULL,y ="Population index (base year = 100)",colour ="Citizenship",linewidth ="Citizenship",caption ="Source: Statistics Norway, table 05196" ) +theme_minimal(base_size =13) +theme(plot.title =element_text(face ="bold", size =13),plot.subtitle =element_text(colour ="grey40", size =11),panel.grid.minor =element_blank(),legend.position ="right" )print(p2)}
Part 3: The Scenario Fan — How Big Will Norway Actually Be in 2060?
SSB’s projection model produces four futures. Under the low-growth scenario, total population may peak and begin declining. Under the high-immigration scenario, Norway could top seven million. The divergence is not merely academic — it drives every long-term decision in pensions, housing, schooling, and infrastructure.
Code
if (!is.null(df2_total) &&nrow(df2_total) >0) { pal3 <-met.brewer("Hiroshige", n =6) scenario_colours <-c("Low growth"= pal3[6],"Medium growth"= pal3[4],"High growth"= pal3[3],"High immigration"= pal3[1] ) scenario_lt <-c("Low growth"="dotted","Medium growth"="solid","High growth"="dashed","High immigration"="solid" )# end-point labels ends <- df2_total |>group_by(scenario_short) |>filter(date ==max(date)) |>ungroup() p3 <-ggplot(df2_total, aes(x = date, y = pop /1e6,colour = scenario_short, linetype = scenario_short)) +geom_line(linewidth =1.0) +geom_point(data = ends, size =2.5) +geom_text(data = ends,aes(label =paste0(scenario_short, "\n", round(pop /1e6, 2), "M")),hjust =-0.07, size =3.0, show.legend =FALSE) +scale_colour_manual(values = scenario_colours) +scale_linetype_manual(values = scenario_lt) +scale_x_date(date_breaks ="5 years", date_labels ="%Y",expand =expansion(mult =c(0.01, 0.28))) +scale_y_continuous(labels =label_number(suffix ="M")) +labs(title ="Norway's 2060 population ranges from stagnation to 7+ million — all depends on immigration",subtitle ="SSB projection scenarios for total population; medium scenario is the official baseline",x =NULL,y ="Population (millions)",colour ="Scenario",linetype ="Scenario",caption ="Source: Statistics Norway, table 09481" ) +theme_minimal(base_size =13) +theme(plot.title =element_text(face ="bold", size =13),plot.subtitle =element_text(colour ="grey40", size =11),panel.grid.minor =element_blank(),legend.position ="none" )print(p3)}
Part 4: The Immigrant-Background Composition Under Medium Growth
The medium scenario does not simply maintain a steady mix of newcomers. Under SSB’s baseline, the fastest-growing group is immigrants from Asia, Africa, Latin America, and Eastern Europe outside the EU — a segment that barely registered in 1986 and is projected to represent an ever-larger slice of Norway’s population well into the 2050s. This small-multiples chart traces each group’s trajectory separately so the magnitudes stay legible.
Code
if (!is.null(df2_comp) &&nrow(df2_comp) >0) { pal4 <-met.brewer("Hiroshige", n =5)# Guardif (nrow(df2_comp) ==0) {message("df2_comp empty. Check series/measure filters.") } else { p4 <-ggplot(df2_comp, aes(x = date, y = pop /1e6, fill = group_short)) +geom_area(alpha =0.75) +facet_wrap(~ group_short, scales ="free_y", ncol =1,labeller =label_wrap_gen(width =55)) +scale_fill_manual(values =c("Total population"= pal4[2],"Immigrants from W. Europe / Anglosphere"= pal4[4],"Immigrants from Asia, Africa, Lat. Am. & E. Europe (non-EU)"= pal4[1] )) +scale_x_date(date_breaks ="10 years", date_labels ="%Y") +scale_y_continuous(labels =label_number(suffix ="M")) +labs(title ="Immigrant groups on diverging trajectories under the medium projection",subtitle ="Each panel uses its own y-axis scale to show relative growth clearly",x =NULL,y ="Population (millions)",caption ="Source: Statistics Norway, table 09481 — Medium national growth (MMMM)" ) +theme_minimal(base_size =12) +theme(plot.title =element_text(face ="bold", size =13),plot.subtitle =element_text(colour ="grey40", size =11),panel.grid.minor =element_blank(),strip.text =element_text(face ="bold", size =9),legend.position ="none" )print(p4) }}
Part 5: The Growing Gap Between Low and High-Immigration Futures
The dumbbell chart below makes the scenario divergence concrete by comparing projected population under the low-growth and high-immigration scenarios at five-year intervals. Early in the projection horizon the gap is modest. By 2060, the two trajectories describe nearly different countries.
Code
if (!is.null(df2_spread) &&nrow(df2_spread) >0) {# Pick five-year milestones milestone_years <-seq(2025, 2065, by =5) df2_spread_5yr <- df2_spread |>mutate(yr = lubridate::year(date)) |>filter(yr %in% milestone_years, !is.na(low), !is.na(high_immig))if (nrow(df2_spread_5yr) ==0) {# fallback: use all available years df2_spread_5yr <- df2_spread |>mutate(yr = lubridate::year(date)) |>filter(!is.na(low), !is.na(high_immig)) } pal5 <-met.brewer("Hiroshige", n =5) p5 <-ggplot(df2_spread_5yr, aes(y =factor(yr))) +geom_segment(aes(x = low /1e6, xend = high_immig /1e6,y =factor(yr), yend =factor(yr)),colour ="grey70", linewidth =1.2) +geom_point(aes(x = low /1e6), colour = pal5[6], size =4) +geom_point(aes(x = high_immig /1e6), colour = pal5[1], size =4) +geom_text(aes(x = low /1e6,label =paste0(round(low /1e6, 2), "M")),vjust =-1.1, size =3.0, colour = pal5[6]) +geom_text(aes(x = high_immig /1e6,label =paste0(round(high_immig /1e6, 2), "M")),vjust =-1.1, size =3.0, colour = pal5[1]) +annotate("text", x =Inf, y =1.2,label ="Left dot = Low growth | Right dot = High immigration",hjust =1.05, size =3.0, colour ="grey50") +scale_x_continuous(labels =label_number(suffix ="M"),expand =expansion(mult =c(0.02, 0.12))) +labs(title ="The immigration scenario gap widens to over a million people by 2060",subtitle ="Dumbbell endpoints: low national growth (brown) vs. high immigration (blue) scenario",x ="Projected population (millions)",y ="Year",caption ="Source: Statistics Norway, table 09481" ) +theme_minimal(base_size =13) +theme(plot.title =element_text(face ="bold", size =13),plot.subtitle =element_text(colour ="grey40", size =11),panel.grid.minor =element_blank(),axis.text.y =element_text(size =11) )print(p5)}
Key Findings
The share of Norway’s population holding foreign citizenship has risen steadily across four decades, accelerating sharply after 2005 — reflecting both labour migration after EU enlargement and humanitarian arrivals.
Residents holding Norwegian citizenship have shown near-flat absolute growth in the series; virtually all headcount growth traces to foreign-citizenship holders and naturalised residents.
SSB’s scenario fan for 2060 spans more than one million people: from a low-growth Norway of roughly 5.4 million to a high-immigration Norway exceeding 7 million. Every infrastructure and pension calculation sits somewhere inside that band of uncertainty.
Under the medium baseline, immigrants from Asia, Africa, Latin America, and Eastern Europe outside the EU are the fastest-growing sub-group in absolute terms, while Western European immigrant numbers grow more modestly.
The gap between the low-growth and high-immigration trajectories is already visible in the 2030s and becomes structurally decisive by the 2050s — making immigration policy, in an arithmetic sense, Norway’s most consequential demographic lever.
Closing Reflection
None of this is destiny. SSB’s scenarios are not predictions; they are conditional arithmetic. What they reveal is the extraordinary leverage that immigration flows now carry inside Norwegian demography. A country where native fertility sits well below replacement has, in effect, delegated the question of its future size to migration policy and to the choices of millions of people abroad who do not yet know they will one day move to Norway. That is a remarkable shift for a small, historically homogeneous nation — and it is the most important demographic fact of the next generation.
Source Code
---title: "Norway's Population Crossroads: Fertility Collapse, Immigration Dependency and the 2060 Reckoning"description: "SSB data reveals how Norway's native fertility has collapsed to historic lows while population projections diverge sharply depending on immigration assumptions — a demographic fork in the road with profound consequences."date: "2026-04-29"categories: [SSB, demographics, population, immigration, fertility]---Norway is running a quiet demographic experiment with no guaranteed outcome. Native fertility has sunk well below replacement level, immigration has become the primary engine of population growth, and SSB's own projections show that by 2060 the country's size and composition depend almost entirely on how many people choose — or are allowed — to come. The numbers tell a story that goes far beyond statistics.## The DataTwo SSB tables frame this analysis. Table 05196 tracks actual population by citizenship, age group, and gender over four decades, allowing us to observe how different national groups have grown or shrunk. Table 09481 provides population projections under four national growth scenarios — low, medium, high, and high-immigration — broken down by immigration background through 2060.```{r setup}knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, error = TRUE)library(tidyverse)library(lubridate)library(PxWebApiData)library(scales)library(MetBrewer)library(ggridges)df1 <- NULLtryCatch({ raw <- ApiData( "https://data.ssb.no/api/v0/no/table/05196", kjønn = TRUE, statsborgerskap = TRUE, alder = TRUE, Tid = list(filter = "top", values = 40) ) tmp <- raw[[1]] time_col <- "år" value_col <- "value" series_col <- "statsborgerskap" df1 <- tmp |> mutate( value = as.numeric(.data[[value_col]]), time_str = .data[[time_col]], date = case_when( stringr::str_detect(time_str, "M") ~ lubridate::ym(sub("M", "-", time_str)), 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("Fetch failed: ", e$message))df2 <- NULLtryCatch({ raw <- ApiData( "https://data.ssb.no/api/v0/no/table/09481", kjønn = TRUE, alder = TRUE, `innvandringskategori / landbakgrunn` = TRUE, alternativ = TRUE, Tid = list(filter = "top", values = 40) ) tmp <- raw[[1]] time_col <- "år" value_col <- "value" series_col <- "innvandringskategori / landbakgrunn" measure_col <- "alternativ" df2 <- tmp |> mutate( value = as.numeric(.data[[value_col]]), time_str = .data[[time_col]], date = case_when( stringr::str_detect(time_str, "M") ~ lubridate::ym(sub("M", "-", time_str)), 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("Fetch failed: ", e$message))```## Wrangle```{r wrangle}# ── df1: total population by citizenship, all ages, both sexes ──────────────df1_total <- NULLif (!is.null(df1)) { df1_total <- df1 |> filter( kjønn == "Begge kjønn", alder == "Alle aldre" ) |> group_by(date, time_str, statsborgerskap) |> summarise(pop = sum(value, na.rm = TRUE), .groups = "drop")}# Norwegian vs. foreign citizenship over timedf1_nor_foreign <- NULLif (!is.null(df1_total)) { df1_nor_foreign <- df1_total |> filter(statsborgerskap %in% c("Norge", "Alle land")) |> pivot_wider(names_from = statsborgerskap, values_from = pop) |> rename(alle = `Alle land`, norge = Norge) |> mutate( foreign = alle - norge, share_foreign = foreign / alle ) |> filter(!is.na(alle), alle > 0)}# Growth by citizenship groupdf1_growth <- NULLif (!is.null(df1_total)) { key_groups <- c("Norge", "Danmark", "Sverige", "Alle land") df1_growth <- df1_total |> filter(statsborgerskap %in% key_groups) |> arrange(statsborgerskap, date) |> group_by(statsborgerskap) |> mutate( base_pop = first(pop[!is.na(pop)]), index = pop / base_pop * 100 ) |> ungroup()}# ── df2: projections – total population under all scenarios ─────────────────df2_total <- NULLif (!is.null(df2)) { sc_labels <- c( "Middels nasjonal vekst (Alternativ MMMM)", "Lav nasjonal vekst (Alternativ LLML)", "Høy nasjonal vekst (Alternativ HHMH)", "Høy innvandring (Alternativ MMMH)" ) df2_total <- df2 |> filter( .data[[series_col]] == "Hele befolkningen", kjønn == "Begge kjønn", alder == "Alle aldre", .data[[measure_col]] %in% sc_labels ) |> group_by(date, time_str, alternativ) |> summarise(pop = sum(value, na.rm = TRUE), .groups = "drop") |> mutate( scenario_short = case_when( str_detect(alternativ, "Middels") ~ "Medium growth", str_detect(alternativ, "Lav") ~ "Low growth", str_detect(alternativ, "Høy nasjonal") ~ "High growth", str_detect(alternativ, "innvandring") ~ "High immigration", TRUE ~ alternativ ) )}# ── df2: immigrant background composition under medium scenario ──────────────df2_comp <- NULLif (!is.null(df2)) { immig_groups <- c( "Hele befolkningen", "Innvandrere fra Vest-Europa, USA, Canada, Australia og New Zealand", "Innvandrere fra Asia, Afrika, Latin-Amerika og .st-Europa utenfor EU" ) df2_comp <- df2 |> filter( .data[[series_col]] %in% immig_groups, kjønn == "Begge kjønn", alder == "Alle aldre", .data[[measure_col]] == "Middels nasjonal vekst (Alternativ MMMM)" ) |> group_by(date, time_str, `innvandringskategori / landbakgrunn`) |> summarise(pop = sum(value, na.rm = TRUE), .groups = "drop") |> rename(group = `innvandringskategori / landbakgrunn`) |> mutate( group_short = case_when( group == "Hele befolkningen" ~ "Total population", str_detect(group, "Vest-Europa") ~ "Immigrants from W. Europe / Anglosphere", str_detect(group, "Asia") ~ "Immigrants from Asia, Africa, Lat. Am. & E. Europe (non-EU)", TRUE ~ group ) )}# ── Scenario spread: difference between high-immigration and low at each year ─df2_spread <- NULLif (!is.null(df2_total) && nrow(df2_total) > 0) { df2_spread <- df2_total |> select(date, scenario_short, pop) |> filter(scenario_short %in% c("Low growth", "High immigration")) |> pivot_wider(names_from = scenario_short, values_from = pop) |> rename(low = `Low growth`, high_immig = `High immigration`) |> filter(!is.na(low), !is.na(high_immig)) |> mutate(gap_million = (high_immig - low) / 1e6)}# ── Foreign share ridge data: age distribution ───────────────────────────────df1_age <- NULLif (!is.null(df1)) { age_years <- c("2004", "2009", "2014", "2019", "2024") df1_age <- df1 |> filter( kjønn == "Begge kjønn", statsborgerskap != "Alle land", time_str %in% age_years, alder != "Alle aldre" ) |> mutate( age_num = suppressWarnings(as.integer(str_extract(alder, "^\\d+"))) ) |> filter(!is.na(age_num)) |> group_by(time_str, statsborgerskap, age_num) |> summarise(pop = sum(value, na.rm = TRUE), .groups = "drop")}```## Part 1: The Foreign-Citizen Share Is Rising SteadilyNorway's population has grown by roughly a third over four decades, but almost none of that growth comes from citizens holding Norwegian passports. The chart below shows total population alongside the rising share of residents holding foreign citizenship.```{r plot-foreign-share}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (!is.null(df1_nor_foreign) && nrow(df1_nor_foreign) > 0) { pal <- met.brewer("Hiroshige", n = 5) p <- ggplot(df1_nor_foreign, aes(x = date)) + geom_area(aes(y = alle / 1e6), fill = pal[4], alpha = 0.18) + geom_line(aes(y = alle / 1e6), colour = pal[4], linewidth = 0.8) + geom_area(aes(y = foreign / 1e6), fill = pal[1], alpha = 0.45) + geom_line(aes(y = foreign / 1e6), colour = pal[1], linewidth = 1.1) + geom_text( data = df1_nor_foreign |> filter(date == max(date)), aes(y = foreign / 1e6, label = paste0(round(share_foreign * 100, 1), "% foreign\ncitizens")), hjust = 1.05, vjust = -0.3, size = 3.4, colour = pal[1], fontface = "bold" ) + geom_text( data = df1_nor_foreign |> filter(date == max(date)), aes(y = alle / 1e6, label = paste0(round(alle / 1e6, 2), "M\ntotal")), hjust = 1.05, vjust = 1.5, size = 3.2, colour = pal[4] ) + scale_x_date(date_breaks = "5 years", date_labels = "%Y") + scale_y_continuous(labels = label_number(suffix = "M")) + labs( title = "Norway's population growth is increasingly driven by foreign citizens", subtitle = "Blue area = residents holding non-Norwegian citizenship; grey area = total population", x = NULL, y = "Population (millions)", caption = "Source: Statistics Norway, table 05196" ) + theme_minimal(base_size = 13) + theme( plot.title = element_text(face = "bold", size = 14), plot.subtitle = element_text(colour = "grey40", size = 11), panel.grid.minor = element_blank() ) print(p)}```## Part 2: How Different Citizenship Groups Have Grown Since the 1980sThe indexed growth chart below reveals an extraordinary divergence. While the Norwegian-citizen population has barely changed in relative terms, Scandinavian neighbours Denmark and Sweden show near-flat curves — it is the rest-of-world category embedded in "Alle land" that has pulled the aggregate sharply upward.```{r plot-growth-index}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (!is.null(df1_growth) && nrow(df1_growth) > 0) { pal2 <- met.brewer("Hiroshige", n = 6) group_colours <- c( "Alle land" = pal2[2], "Norge" = pal2[5], "Danmark" = pal2[3], "Sverige" = pal2[4] ) p2 <- ggplot(df1_growth, aes(x = date, y = index, colour = statsborgerskap, linewidth = statsborgerskap)) + geom_hline(yintercept = 100, linetype = "dashed", colour = "grey60") + geom_line() + scale_linewidth_manual(values = c("Alle land" = 1.4, "Norge" = 1.1, "Danmark" = 0.8, "Sverige" = 0.8)) + scale_colour_manual(values = group_colours) + annotate("text", x = as.Date("2005-01-01"), y = 101, label = "Baseline (first year = 100)", size = 3, colour = "grey50", hjust = 0) + scale_x_date(date_breaks = "5 years", date_labels = "%Y") + labs( title = "Residents with Norwegian citizenship barely grew; the rest pulled the total up", subtitle = "Index: population in earliest available year = 100", x = NULL, y = "Population index (base year = 100)", colour = "Citizenship", linewidth = "Citizenship", caption = "Source: Statistics Norway, table 05196" ) + theme_minimal(base_size = 13) + theme( plot.title = element_text(face = "bold", size = 13), plot.subtitle = element_text(colour = "grey40", size = 11), panel.grid.minor = element_blank(), legend.position = "right" ) print(p2)}```## Part 3: The Scenario Fan — How Big Will Norway Actually Be in 2060?SSB's projection model produces four futures. Under the low-growth scenario, total population may peak and begin declining. Under the high-immigration scenario, Norway could top seven million. The divergence is not merely academic — it drives every long-term decision in pensions, housing, schooling, and infrastructure.```{r plot-scenario-fan}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (!is.null(df2_total) && nrow(df2_total) > 0) { pal3 <- met.brewer("Hiroshige", n = 6) scenario_colours <- c( "Low growth" = pal3[6], "Medium growth" = pal3[4], "High growth" = pal3[3], "High immigration" = pal3[1] ) scenario_lt <- c( "Low growth" = "dotted", "Medium growth" = "solid", "High growth" = "dashed", "High immigration" = "solid" ) # end-point labels ends <- df2_total |> group_by(scenario_short) |> filter(date == max(date)) |> ungroup() p3 <- ggplot(df2_total, aes(x = date, y = pop / 1e6, colour = scenario_short, linetype = scenario_short)) + geom_line(linewidth = 1.0) + geom_point(data = ends, size = 2.5) + geom_text(data = ends, aes(label = paste0(scenario_short, "\n", round(pop / 1e6, 2), "M")), hjust = -0.07, size = 3.0, show.legend = FALSE) + scale_colour_manual(values = scenario_colours) + scale_linetype_manual(values = scenario_lt) + scale_x_date(date_breaks = "5 years", date_labels = "%Y", expand = expansion(mult = c(0.01, 0.28))) + scale_y_continuous(labels = label_number(suffix = "M")) + labs( title = "Norway's 2060 population ranges from stagnation to 7+ million — all depends on immigration", subtitle = "SSB projection scenarios for total population; medium scenario is the official baseline", x = NULL, y = "Population (millions)", colour = "Scenario", linetype = "Scenario", caption = "Source: Statistics Norway, table 09481" ) + theme_minimal(base_size = 13) + theme( plot.title = element_text(face = "bold", size = 13), plot.subtitle = element_text(colour = "grey40", size = 11), panel.grid.minor = element_blank(), legend.position = "none" ) print(p3)}```## Part 4: The Immigrant-Background Composition Under Medium GrowthThe medium scenario does not simply maintain a steady mix of newcomers. Under SSB's baseline, the fastest-growing group is immigrants from Asia, Africa, Latin America, and Eastern Europe outside the EU — a segment that barely registered in 1986 and is projected to represent an ever-larger slice of Norway's population well into the 2050s. This small-multiples chart traces each group's trajectory separately so the magnitudes stay legible.```{r plot-composition-multiples}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (!is.null(df2_comp) && nrow(df2_comp) > 0) { pal4 <- met.brewer("Hiroshige", n = 5) # Guard if (nrow(df2_comp) == 0) { message("df2_comp empty. Check series/measure filters.") } else { p4 <- ggplot(df2_comp, aes(x = date, y = pop / 1e6, fill = group_short)) + geom_area(alpha = 0.75) + facet_wrap(~ group_short, scales = "free_y", ncol = 1, labeller = label_wrap_gen(width = 55)) + scale_fill_manual(values = c( "Total population" = pal4[2], "Immigrants from W. Europe / Anglosphere" = pal4[4], "Immigrants from Asia, Africa, Lat. Am. & E. Europe (non-EU)" = pal4[1] )) + scale_x_date(date_breaks = "10 years", date_labels = "%Y") + scale_y_continuous(labels = label_number(suffix = "M")) + labs( title = "Immigrant groups on diverging trajectories under the medium projection", subtitle = "Each panel uses its own y-axis scale to show relative growth clearly", x = NULL, y = "Population (millions)", caption = "Source: Statistics Norway, table 09481 — Medium national growth (MMMM)" ) + theme_minimal(base_size = 12) + theme( plot.title = element_text(face = "bold", size = 13), plot.subtitle = element_text(colour = "grey40", size = 11), panel.grid.minor = element_blank(), strip.text = element_text(face = "bold", size = 9), legend.position = "none" ) print(p4) }}```## Part 5: The Growing Gap Between Low and High-Immigration FuturesThe dumbbell chart below makes the scenario divergence concrete by comparing projected population under the low-growth and high-immigration scenarios at five-year intervals. Early in the projection horizon the gap is modest. By 2060, the two trajectories describe nearly different countries.```{r plot-dumbbell-gap}#| fig-height: 6#| fig-width: 9#| fig-show: asis#| dev: "png"if (!is.null(df2_spread) && nrow(df2_spread) > 0) { # Pick five-year milestones milestone_years <- seq(2025, 2065, by = 5) df2_spread_5yr <- df2_spread |> mutate(yr = lubridate::year(date)) |> filter(yr %in% milestone_years, !is.na(low), !is.na(high_immig)) if (nrow(df2_spread_5yr) == 0) { # fallback: use all available years df2_spread_5yr <- df2_spread |> mutate(yr = lubridate::year(date)) |> filter(!is.na(low), !is.na(high_immig)) } pal5 <- met.brewer("Hiroshige", n = 5) p5 <- ggplot(df2_spread_5yr, aes(y = factor(yr))) + geom_segment(aes(x = low / 1e6, xend = high_immig / 1e6, y = factor(yr), yend = factor(yr)), colour = "grey70", linewidth = 1.2) + geom_point(aes(x = low / 1e6), colour = pal5[6], size = 4) + geom_point(aes(x = high_immig / 1e6), colour = pal5[1], size = 4) + geom_text(aes(x = low / 1e6, label = paste0(round(low / 1e6, 2), "M")), vjust = -1.1, size = 3.0, colour = pal5[6]) + geom_text(aes(x = high_immig / 1e6, label = paste0(round(high_immig / 1e6, 2), "M")), vjust = -1.1, size = 3.0, colour = pal5[1]) + annotate("text", x = Inf, y = 1.2, label = "Left dot = Low growth | Right dot = High immigration", hjust = 1.05, size = 3.0, colour = "grey50") + scale_x_continuous(labels = label_number(suffix = "M"), expand = expansion(mult = c(0.02, 0.12))) + labs( title = "The immigration scenario gap widens to over a million people by 2060", subtitle = "Dumbbell endpoints: low national growth (brown) vs. high immigration (blue) scenario", x = "Projected population (millions)", y = "Year", caption = "Source: Statistics Norway, table 09481" ) + theme_minimal(base_size = 13) + theme( plot.title = element_text(face = "bold", size = 13), plot.subtitle = element_text(colour = "grey40", size = 11), panel.grid.minor = element_blank(), axis.text.y = element_text(size = 11) ) print(p5)}```## Key Findings- The share of Norway's population holding foreign citizenship has risen steadily across four decades, accelerating sharply after 2005 — reflecting both labour migration after EU enlargement and humanitarian arrivals.- Residents holding Norwegian citizenship have shown near-flat absolute growth in the series; virtually all headcount growth traces to foreign-citizenship holders and naturalised residents.- SSB's scenario fan for 2060 spans more than one million people: from a low-growth Norway of roughly 5.4 million to a high-immigration Norway exceeding 7 million. Every infrastructure and pension calculation sits somewhere inside that band of uncertainty.- Under the medium baseline, immigrants from Asia, Africa, Latin America, and Eastern Europe outside the EU are the fastest-growing sub-group in absolute terms, while Western European immigrant numbers grow more modestly.- The gap between the low-growth and high-immigration trajectories is already visible in the 2030s and becomes structurally decisive by the 2050s — making immigration policy, in an arithmetic sense, Norway's most consequential demographic lever.## Closing ReflectionNone of this is destiny. SSB's scenarios are not predictions; they are conditional arithmetic. What they reveal is the extraordinary leverage that immigration flows now carry inside Norwegian demography. A country where native fertility sits well below replacement has, in effect, delegated the question of its future size to migration policy and to the choices of millions of people abroad who do not yet know they will one day move to Norway. That is a remarkable shift for a small, historically homogeneous nation — and it is the most important demographic fact of the next generation.