India’s Foodgrain Buffer Stocks: Trends, Norms, and Policy

Food Economy
Agriculture
Public Policy
Interactive analysis of India’s central pool foodgrain stocks — rice, wheat, unmilled paddy, and coarse grains — from 1972 to 2025, tracking stock positions against buffer norms and examining recent seasonal patterns.
Author

Pawan

Published

November 1, 2025

Modified

March 29, 2026

Overview

India maintains one of the world’s largest publicly managed foodgrain buffer stock systems. The Food Corporation of India (FCI) and state agencies procure, store, and distribute wheat and rice to approximately 800 million beneficiaries under the National Food Security Act (NFSA). The central pool stock position — reported monthly by FCI — is a critical indicator of India’s food security preparedness, the government’s procurement performance, and the adequacy of storage infrastructure.


TipKey Findings at a Glance
  • 📦 Total foodgrain stocks peaked at over 100 LMT (10 MT) in early 2025, driven by large unmilled paddy holdings
  • 🌾 Wheat stocks show sharp seasonal cycles: declining through the year as distribution continues, then replenishing sharply after the April–June procurement season
  • 🍚 Rice stocks have grown steadily from ~21 LMT (early 2023) to ~34 LMT (early 2025)
  • ⚠️ Wheat stocks hit a near-decade low of ~7.5 LMT in April 2024, raising brief food security concerns before the 2024 rabi harvest
  • 📊 Stocks consistently exceed buffer norms: India over-procures relative to minimum requirements, creating storage pressure and fiscal costs
  • 🌾 Unmilled paddy (dhaan) holdings have grown as procurement has expanded from traditional rice-bowl states to eastern India

1 Setup

Code
library(data.table)
library(ggplot2)
library(ggiraph)
library(tidyr)
library(scales)
library(lubridate)
library(DT)

my_theme <- theme_minimal(base_size = 13) +
  theme(
    text             = element_text(color = "#2c3e50"),
    plot.title       = element_text(size = 16, face = "bold", hjust = 0.5),
    plot.subtitle    = element_text(size = 11, hjust = 0.5, color = "#7f8c8d"),
    plot.caption     = element_text(size = 9, color = "#95a5a6", hjust = 0),
    axis.title       = element_text(size = 12, face = "bold"),
    axis.text        = element_text(size = 10),
    axis.text.x      = element_text(angle = 45, vjust = 1, hjust = 1),
    legend.position  = "bottom",
    legend.title     = element_blank(),
    panel.grid.major = element_line(color = "#ecf0f1", linewidth = 0.5),
    panel.grid.minor = element_blank(),
    strip.background = element_rect(fill = "#f8f9fa", color = NA),
    strip.text       = element_text(face = "bold")
  )

std_opts <- list(
  opts_tooltip(
    css = paste0("background-color:#2c3e50;color:white;padding:8px 12px;",
                 "border-radius:6px;font-size:12px;line-height:1.5;")
  ),
  opts_hover(css = "fill-opacity:0.85;stroke:white;stroke-width:1;"),
  opts_hover_inv(css = "opacity:0.2;"),
  opts_zoom(min = 0.5, max = 3),
  opts_toolbar(position = "topright"),
  opts_sizing(rescale = TRUE, width = 1)
)

2 Data Loading and Preparation

Code
## Load current stocks data
stocksall <- fread("~/stocks/stocksall.csv", sep = ",", header = TRUE)
stocksall <- unique(stocksall)

## Load historical data
oldstocks <- fread("~/stocks/pdsold.csv", sep = ",", header = TRUE)
oldstocks[, coarsegrains := stocks_total - stocks_rice - stocks_wheat]

## Reshape current stocks to long format
stocks_long <- melt(stocksall, id = c("Year", "Commodity"))
stocks_long[, Date := as.Date(paste("01", variable, Year, sep = "-"), "%d-%b-%Y")]
stocks_long[, Commodity := factor(Commodity,
  levels = c("Rice", "Wheat", "Unmilled Paddy", "Coarsegrain", "Total"))]

## Process historical data
oldstocks_clean <- separate(oldstocks, col = Year, sep = "-", into = c("v1", "year"))
setDT(oldstocks_clean)
oldstocks_clean[, Date := as.Date(paste("01", "04", year, sep = "-"), "%d-%m-%y")]
oldstocks_clean <- oldstocks_clean[Date < as.Date("2003-01-01"),
  .(Date, Rice = stocks_rice, Wheat = stocks_wheat, Coarsegrain = coarsegrains)]
old_long <- melt(oldstocks_clean, id = "Date", variable.name = "Commodity")
old_long[, value := value * 10]   ## → lakh tonnes
old_long[, Commodity := as.character(Commodity)]

## Filter and combine
new_long <- stocks_long[Commodity != "Total", .(Date, Commodity = as.character(Commodity), value)]
new_long <- new_long[!is.na(value)]
old_long <- old_long[!is.na(value)]

combined <- rbind(new_long, old_long)
combined[, value := as.numeric(value) / 10]   ## → million tonnes
combined <- combined[!is.na(value)]
combined[, Commodity := factor(Commodity,
  levels = rev(c("Rice", "Wheat", "Unmilled Paddy", "Coarsegrain")))]

## Build tooltip
combined[, tip := paste0("<b>", Commodity, "</b><br>",
                         "Date: ", format(Date, "%b %Y"), "<br>",
                         "Stock: <b>", round(value, 2), " MT</b>")]

3 Full Historical Stock Position (1972–2025)

Code
p_full <- ggplot(combined, aes(x = Date, y = value, fill = Commodity,
                                tooltip = tip, data_id = paste(Date, Commodity),
                                group = Commodity)) +
  geom_area_interactive(alpha = 0.85, color = "white", linewidth = 0.3,
                        position = "stack") +
  scale_fill_manual(values = c(
    "Rice"          = "#4E79A7",
    "Wheat"         = "#F28E2B",
    "Unmilled Paddy" = "#E15759",
    "Coarsegrain"   = "#76B7B2"
  )) +
  ## Mark major policy events
  annotate("rect", xmin = as.Date("2001-01-01"), xmax = as.Date("2003-01-01"),
           ymin = -Inf, ymax = Inf, fill = "#e74c3c", alpha = 0.07) +
  annotate("text", x = as.Date("2002-01-01"), y = 75,
           label = "2002\nDrought", size = 3, color = "#c0392b", angle = 90) +
  scale_x_date(date_breaks = "5 years", date_labels = "%Y") +
  scale_y_continuous("Stocks (Million Tonnes)", labels = comma_format(),
                     expand = expansion(mult = c(0, 0.05))) +
  labs(title   = "India's Central Pool Foodgrain Stock Position (1972–2025)",
       subtitle = "Stacked area — hover for commodity breakdown by date",
       x = "Year",
       caption = "Source: Food Corporation of India (FCI) | Analysis: Pawan") +
  my_theme

girafe(ggobj = p_full, width_svg = 14, height_svg = 7, options = std_opts)

6 Last 24 Months — Interactive Data Table

Code
last24 <- combined[Date >= as.Date("2023-04-01")]
last24_wide <- dcast(last24, Date ~ Commodity, value.var = "value", fun.aggregate = sum)
last24_wide[, Total := rowSums(.SD, na.rm = TRUE), .SDcols = setdiff(names(last24_wide), "Date")]
last24_wide[, Date := format(Date, "%b-%Y")]

datatable(
  last24_wide,
  rownames = FALSE,
  extensions = "Buttons",
  options = list(
    pageLength = 12,
    dom = "Bfrtip",
    buttons = c("copy", "csv", "excel"),
    scrollX = TRUE
  ),
  caption = htmltools::tags$caption(
    style = "caption-side:top;text-align:center;font-size:15px;font-weight:bold;",
    "Central Pool Foodgrain Stocks — Last 24 Months (Million Tonnes)"
  )
) |>
  formatRound(columns = setdiff(names(last24_wide), "Date"), digits = 2)

7 Key Takeaways

ImportantSummary of Findings
  1. India’s central pool stocks are at historically high levels relative to buffer norms, reflecting several consecutive good harvests and aggressive procurement — but also significant storage infrastructure pressure and fiscal carrying costs.

  2. Wheat stocks follow a pronounced seasonal cycle: they decline through the year as PDS offtake and open market sales proceed, then jump sharply in April–June as the rabi wheat crop is procured. The April 2024 dip to ~7.5 LMT wheat was the lowest in nearly a decade — a reminder that even apparently comfortable stock positions can dissipate rapidly.

  3. Rice and unmilled paddy stocks have grown substantially, driven by the expansion of paddy procurement from eastern states (Chhattisgarh, Telangana, Odisha, West Bengal) under the Decentralised Procurement scheme.

  4. Unmilled paddy holdings represent a distinctive feature of India’s system: paddy is often stored unmilled until conversion to rice is needed, creating a large but operationally more complex buffer than milled rice holdings.

  5. COVID-19 (2020) did not disrupt stocks — the government used its large pre-pandemic stock position to mount the PM-GKAY free grain distribution scheme, demonstrating the food security value of the buffer stock system.

  6. The coarse grain stock position remains negligible despite policy attention to millets — the procurement system for coarse grains outside a few states remains underdeveloped.