Offline-first application

Grocery Queue

A predictive inventory queue that figures out what is disappearing from your kitchen and helps decide which store is worth walking to next.

Most grocery apps are checklists.

Grocery Queue is a model of what is disappearing from your kitchen.

Instead of manually deciding what belongs on a shopping list, you tell the app what you have, give it a rough initial estimate of how long it should last, and then update it occasionally as you buy and consume things. It estimates when each item will run out and continuously ranks everything by urgency.

The result is not a list of things you might buy someday. It is a queue of what is becoming important next.

The problem: which store is worth walking to today?

The app came from a small but persistent annoyance. I have several grocery stores within walking distance and visit one almost every day, but choosing one still required reconstructing the state of my kitchen in my head.

The useful question was not merely What groceries do I need? It was:

Given what I am running out of, which store is most worth walking to today?

That seemed like exactly the kind of problem software should eliminate.

One queue, not several shopping lists

Different groceries have different store constraints. Milk and eggs belong to Whole Foods, ground beef to Trader Joe’s, and liver to Publix, while potatoes and bananas can come from several stores.

Before Grocery Queue, deciding where to go meant mentally scanning several overlapping lists and figuring out what was actually urgent.

Now there is one global queue. If its most urgent items cluster at Trader Joe’s, that is probably today’s walk. If liver is about to run out, Publix becomes relevant. If I already know I am going to Whole Foods, I can filter the same queue to see what matters there.

The stores are not separate shopping lists. They are constraints on the same underlying inventory model.

From inventory to urgency

For each item, Grocery Queue estimates a depletion rate rr and a current inventory q^(t)\widehat q(t). The simplest intuition is

(q^(t),r)⟶Tleft≈q^(t)r⟶priority.(\widehat q(t),r) \longrightarrow T_{\mathrm{left}} \approx \frac{\widehat q(t)}{r} \longrightarrow \text{priority}.

Something with three weeks left should mostly disappear from my attention. Something likely to run out tomorrow should move toward the top.

That is the central distinction from an ordinary shopping list: a shopping list stores intentions; Grocery Queue stores an estimate of a changing physical state and predicts when intervention will be necessary.

Learning from sparse observations

The initial depletion rate is deliberately only a guess. As I use the app, actual observations gradually replace it.

The important trick is that purchases and counts provide different information.

Suppose the last exact count was q0q_0 at time t0t_0, I purchased a total of BB units since then, and the next count is q1q_1 at time t1t_1.

The amount actually consumed is

c=q0+B−q1,c = q_0 + B - q_1,

over the interval

Δt=t1−t0.\Delta t = t_1 - t_0.

So a purchase does not itself tell the model how much I consumed. It only tells it that inventory increased. Consumption becomes observable when another count closes the interval.

Grocery Queue maintains two weighted sufficient statistics: accumulated consumption evidence CC and accumulated time evidence DD. The estimated depletion rate is simply

r=CD.r = \frac{C}{D}.

When a new count arrives, old evidence is exponentially discounted and the new observation is added:

C′=λC+c,D′=λD+Δt,C' = \lambda C + c, \qquad D' = \lambda D + \Delta t,

where

λ=2−(t1−tE)/H.\lambda = 2^{-(t_1-t_E)/H}.

Here tEt_E is the time of the previous evidence update and HH is the evidence half-life.

The half-life adapts to the item. Grocery Queue estimates a typical purchase cycle from recent purchase intervals and uses

H=Tcycle.H = T_{\text{cycle}}.

The cycle is estimated from the median of up to the five most recent valid purchase intervals.

That means something I buy every few days can adapt relatively quickly, while something I buy every few months retains useful historical evidence for much longer.

The conceptual separation is simple:

Counts tell the model how much was consumed. Purchase quantities maintain inventory. Purchase timing determines how quickly old evidence should be forgotten.

Predicting what remains

Inventory estimation is anchored to the most recent exact count.

Let qAq_A be that count, recorded at time tAt_A, and let BAB_A be everything purchased since then. At time tt, estimated inventory is

q^(t)=max⁡ ⁣(0, qA+BA−r(t−tA)).\widehat q(t) = \max\!\left(0,\ q_A+B_A-r(t-t_A)\right).

The predicted time remaining is then

Tleft=q^(t)rfor r>0.T_{\text{left}} = \frac{\widehat q(t)}{r} \qquad \text{for } r>0.

That value ultimately drives the queue.

So underneath a very mundane grocery interface is a small state-estimation problem: infer the current inventory from sparse observations, learn its dynamics, predict when it reaches zero, and rank the actions that will soon become necessary.

Constant-space learning

One property I particularly wanted was that the model should not grow with the history of the item. History is evidence used to update the model; history is not itself the model.

At any moment, the relevant state is represented by the sufficient statistics CC and DD, plus a small fixed amount of recent purchase-cycle information. The app does not need to retain every egg count, carton of milk, or purchase event to estimate the current depletion rate.

Updating the model is therefore O(1)O(1) in the number of historical purchase and inventory events. Whether an item has been tracked for two weeks or five years, the work required for a new observation does not grow with its history.

That matters both computationally and conceptually: the application maintains a compact model of the present instead of accumulating an ever-growing log and repeatedly recomputing the past.

Local by design

Grocery Queue has no account, server, or cloud database. The model lives in the browser.

After the first visit, the complete application works offline and can be installed as a PWA.

That fits the use case: this is something I open while walking around and shopping. It should not need a network connection, login, synchronization service, or backend just to tell me whether I need eggs.

Grocery Queue turns the kitchen into a continuously changing priority queue:

see what is disappearing, see where it can be replaced, and pick today’s walk.