Perishable inventory and expiration tracking: a retailer's guide to FIFO, FEFO, and waste
Perishable inventory expiration tracking is the practice of recording, monitoring, and rotating stock by expiry date so that perishable goods sell before they spoil β and so that the unavoidable spoilage that does occur is measured, attributed, and reduced over time. For grocery, convenience, and food retail, expiration tracking is the difference between a managed cost and a silent margin leak.
Most retailers know they have a spoilage problem. Few measure it accurately. The ones that do typically discover spoilage running 3β8% of perishable category revenue β a figure that, once visible, becomes actionable.
The cost of unmanaged perishable inventory
A grocery store running 35% of revenue through fresh categories (dairy, bakery, produce, deli) and accepting industry-typical 5% spoilage is losing roughly 1.75% of total revenue to expired goods. On a store doing $2M annual revenue, that's $35,000 walking out the back door every year as compost.
Three things make perishable losses worse than they look on a P&L:
- Spoilage often hides inside shrinkage. Stores that don't separately track waste from theft conflate the two. The 5% "shrinkage" line item gets attributed to staff or customers when much of it is bread that nobody bought in time.
- Markdowns happen too late. Items priced down with two days of shelf life left rarely sell at full velocity; items priced down with five days left often sell through entirely. Without expiry visibility, markdown timing is reactive.
- Reorder quantities don't adjust. A category that runs 8% spoilage every month is being over-ordered. Without expiry-aware reporting, the over-ordering pattern persists.
Fixing any of these requires the foundational ability to track each batch of perishable inventory by expiry date β not just SKU and quantity. That's what batch-level inventory provides.
Batch-level inventory: the foundation of expiration tracking
Standard inventory tracks (SKU, quantity, location). Batch-level inventory tracks (SKU, quantity, location, batch_id, supplier, received_date, expiry_date, lot_number) β the same SKU can have multiple records, one per batch, each with its own expiry.
When a delivery of yogurt arrives:
- 80 units, expiry 2026-05-25, supplier A β batch B-1041
- 60 units, expiry 2026-06-02, supplier A β batch B-1042
Both batches sell as the same product to customers. Internally they're separate inventory records. When a unit sells, the POS depletes from the oldest expiry batch first. When the older batch is empty, sales start depleting the newer one.
This is the data structure that makes everything else possible: FIFO/FEFO rotation, expiry alerts, waste reports, and category-level shelf-life analysis. Without batch tracking, "we should sell the older yogurt first" is a verbal instruction at handover; with batch tracking, it's enforced by the system.
A grocery POS like Sandooq stores batch records natively, so receiving a delivery captures expiry dates as part of the supplier and goods-received workflow β not as a separate spreadsheet. Get started with Sandooq today.
FIFO vs FEFO: which rotation method retail uses
FIFO (First In, First Out) and FEFO (First Expired, First Out) are the two stock rotation policies. They sound similar and produce different results.
FIFO rotates by receipt date. The first batch received is the first batch sold. This works for products where the manufacturer dates and shelf lives are consistent β older batches are also closer to expiry simply because they were made earlier.
FEFO rotates by expiry date. The batch with the earliest expiry is the first batch sold, regardless of when it was received. This handles edge cases where a newer delivery has a shorter shelf life than what's already on the shelf β common with imported goods, supplier shelf-life inconsistencies, or seasonal restocking.
For most grocery retail, FEFO is the safer default. It always sells the highest-risk inventory first. FIFO can leave a near-expiry batch unsold while a fresher (but later-received) batch sells through.
The POS should suggest the rotation order to staff during shelf restocking β not just at the cashier. A system that knows batch B-1041 expires 2026-05-25 should print picking lists or shelf-rotation alerts that surface that batch first. This is where many implementations stop short: they track expiry but don't operationalize it into staff workflow.
Expiry alerts and markdown workflows
A useful expiry alert system tiers warnings by risk:
| Days to expiry | Action |
|---|---|
| 30+ | No action β normal selling |
| 14β30 | Visibility only on the inventory dashboard |
| 7β14 | Daily report to category manager; consider promotion |
| 3β7 | Markdown candidate list β staff applies discount |
| 1β3 | Aggressive markdown (30β50% off) or write-off prep |
| 0 | Pull from shelf, write off as waste |
The thresholds vary by category. Bakery items operate in days; canned goods in months. The structure stays the same.
Markdown timing matters more than markdown depth. A 20% discount applied 5 days before expiry typically clears the batch; a 50% discount applied the day before expiry rarely does. The POS should surface the markdown decision early enough for staff to act β and should track which markdowns cleared the batch versus which ones still ended in waste, so the timing rules can be tuned over time.
Pair markdown workflows with POS sales reports and retail analytics to measure markdown effectiveness by category, by season, and by markdown depth. The data shows whether your markdowns are clearing inventory or just discounting goods that would have sold anyway.
Waste vs shrinkage: tracking spoilage separately
Inventory loss has multiple sources. Conflating them hides where the actual problem is.
| Loss type | Cause | Measurement |
|---|---|---|
| Waste / spoilage | Item expired or damaged | Write-off recorded against batch with reason "expired" or "damaged" |
| Theft / shrinkage | Internal or external theft, miscount | Variance between expected and actual on-hand at cycle count |
| Markdown loss | Item discounted to clear before expiry | Margin reduction recorded as part of the sale |
| Receiving error | Supplier under-delivered, wasn't caught at receipt | Goods-received note variance |
A POS that lumps all of these into "shrinkage" gives management one big number with no diagnostic value. A POS that separates them produces a category-level breakdown:
- Bakery: 4.2% waste, 0.3% shrinkage, 1.1% markdown loss
- Dairy: 2.8% waste, 0.4% shrinkage, 0.6% markdown loss
- Produce: 6.1% waste, 0.5% shrinkage, 2.3% markdown loss
Now you know that produce is the priority β and specifically that waste, not shrinkage, is the mechanism. The intervention is different: tighter receiving on produce, smaller order quantities, faster markdowns. None of those would surface from a single shrinkage number.
For the broader fraud-and-shrinkage angle, see our loss prevention guide. Waste and shrinkage are different problems with different solutions; the point is to measure each separately.
Category-specific expiration windows
Not every perishable behaves the same way. Setting a single expiry-alert rule for all perishables produces alert fatigue in some categories and missed warnings in others.
Realistic shelf-life windows by category:
| Category | Typical shelf life | Warning at | Markdown at |
|---|---|---|---|
| Fresh bakery | 2β3 days | Day 1 remaining | End of day 1 |
| Pre-packaged bread | 5β7 days | 2 days remaining | 1 day remaining |
| Dairy (yogurt, milk) | 14β21 days | 5 days remaining | 3 days remaining |
| Fresh meat | 3β5 days | 2 days remaining | 1 day remaining |
| Cured meat / deli | 14β30 days | 5 days remaining | 3 days remaining |
| Fresh produce | 5β10 days | 2 days remaining | Same day |
| Frozen goods | 6β12 months | 30 days remaining | 14 days remaining |
| Dry goods (canned, packaged) | 12β24 months | 60 days remaining | 30 days remaining |
The POS should support per-category expiry rules β not a single global threshold. This is configuration work done once during setup; it pays back every day after.
For grocery operators expanding from a single store to multiple, the category rules need to be set at the network level and apply consistently across all locations. One store running tight bakery rules and another running loose ones creates inconsistent customer experience and uneven margin pressure.
Frequently asked questions
How do grocery POS systems track perishable items and expiration dates?
A grocery POS that supports perishables tracks inventory at the batch level, not just by SKU. Each delivery records the SKU, quantity, supplier, received date, and expiry date as a separate batch record. The POS then surfaces approaching-expiry alerts based on category-specific rules, depletes inventory using FIFO or FEFO logic, and reports waste separately from theft-related shrinkage. Without batch-level tracking, "expiration tracking" collapses to a manual shelf check that staff shortcuts when the store is busy.
What's the difference between FIFO and FEFO?
FIFO (First In, First Out) rotates by receipt date β the oldest batch by arrival sells first. FEFO (First Expired, First Out) rotates by expiry date β the batch closest to expiring sells first, regardless of arrival. FIFO works when shelf lives are consistent across deliveries; FEFO is safer when supplier shelf lives vary or when imported goods have variable expiry. For most grocery retail, FEFO is the preferred default because it always sells the highest-risk inventory first.
What is shelf-life expiration tracking?
Shelf-life expiration tracking is the operational practice of recording each batch of perishable inventory with its expiry date, monitoring days-to-expiry continuously, and triggering rotation, markdown, or write-off actions based on category-specific thresholds. It requires batch-level inventory in the POS, category-specific alert rules, and integration into staff workflows (picking lists, markdown reports, end-of-day waste recording).
How early should expiry alerts trigger?
Alert thresholds vary by category. Fresh bakery needs alerts within 1 day of remaining shelf life; dairy needs 5 days; cured meats 7β10 days; dry goods 30β60 days. The principle is: trigger early enough to act (markdown, transfer to a higher-velocity location, promote in marketing), but late enough to avoid alert fatigue. Most retailers set two tiers β a soft "watch" alert and a hard "act now" alert β per category.
Should expired items be written off as shrinkage or waste?
As waste, separately from shrinkage. Conflating expired items with theft-related shrinkage gives you one big number with no diagnostic value. A POS that records write-offs with a reason code ("expired", "damaged", "spoiled") produces a clear category-level view of where margin is actually leaking. Once waste and shrinkage are separated, the interventions become different β tighter receiving and faster markdowns for waste; access controls and audit logs for shrinkage.
Can perishables be tracked manually instead of in a POS?
For a single-shelf operation, yes β a clipboard and a daily shelf walk can work. Beyond that, manual tracking breaks down: the staff time required scales linearly with SKU count, the records are easy to fudge, and the data isn't queryable for category-level analysis. Most stores carrying 100+ perishable SKUs find that the time saved by automated batch tracking pays back the cost of the POS system within a quarter.
What categories need the strictest expiration tracking?
Fresh bakery, fresh meat, and fresh produce β all three combine short shelf lives (2β10 days) with high margin sensitivity. Bakery is especially unforgiving because shelf life is measured in hours toward the end. Dairy is medium-strict (14β21 days). Frozen and dry goods are loosest because the shelf-life window absorbs slower rotation. Set the strictest rules where the consequence of missing an alert is total write-off within 24 hours.
Track perishables at the batch level β not just by SKU. Learn how Sandooq simplifies cash management, inventory, and expiration tracking in one POS. Pair this with our guides on the grocery POS system as a whole, inventory cycle counts, and retail loss prevention.
Authoritative sources
- Saudi Food and Drug Authority (SFDA) β food labeling, expiry, and storage rules in Saudi Arabia.
- UAE Federal Authority for Food Safety β UAE food handling, traceability, and expiry labeling rules.
- Codex Alimentarius β international food standards β global food safety and labeling standards including shelf life.
- GS1 β batch and expiry data identifiers (AI 10, AI 17) β GS1 Application Identifier standards for batch number and expiry date encoding.
Related guides
Related Articles

Cycle stock formula: how to calculate safety stock and reorder points for retail
Cycle stock is the inventory you expect to sell between replenishment orders. Learn the formula, how it differs from cycle counting, and how to size safety stock and reorder points.
Back to Blog
Demand forecasting for retail: turning POS data into stock decisions
Your POS already records what sells, when, and where. A practical guide to turning that data into reorder decisions that prevent stockouts without overbuying.
Back to Blog
Multi-store inventory management: how to keep stock accurate across locations
Practical strategies for tracking inventory across multiple retail locations without spreadsheets or manual reconciliation.
Back to Blog
How cycle counts prevent end-of-month inventory surprises
Use lightweight weekly counting routines to keep stock records accurate and reduce urgent corrections.
Back to Blog
Grocery POS supplier integration: purchase orders, goods-received notes, and cost flow
Grocery POS supplier integration connects purchase orders, deliveries, and invoices to the POS so inventory and margin update automatically. Here is what the workflow looks like end-to-end.
Back to Blog
Gift cards and store credit: how to set them up in your POS
Gift cards and store credit are simple revenue tools that quietly lift retention and average ticket. A practical guide to setup, redemption, accounting, and the regulations to watch.
Back to Blog