All posts
Sep 12, 2026·3 min read

Salt doesn't expire, and it broke my kitchen app

#app-design#lessons-learned#side-projects

I built an app that tracks the food in our house. Snap a picture of a receipt or a shelf, it figures out what you bought, and the pantry list tells you what's about to go bad.

Every item on the shelf has an expiration date. A blank one means nobody has entered it yet, and the app logs it as a gap to fill later. That worked fine for months.

Then I added salt.

Salt doesn't expire. Neither do sugar, honey, or white vinegar. The app had no way to say that, so the salt sat in the pantry list reading "no date" like someone just hadn't gotten around to it, and my list of dates to chase down filled up with food that should never get one.

One blank field, two meanings

The salt was just what exposed it. The real bug was that an empty field meant two different things:

  • We don't know the date yet. Something's missing, go find out.
  • There is no date. Nothing's missing, that's the answer.

They look identical in the database and need opposite handling. One should nag you and the other should make the nagging stop. I'd only built the first meaning, so the app was wrong in both directions at once: a false alarm on the salt, and real gaps buried under fake ones.

Put the flag where the fact lives

The fix was a "doesn't expire" flag, and the important decision was putting it on the food instead of on the individual container.

Salt never expiring is true of all salt, whatever box it came in. Put the flag on the container and you'd be re-checking the same box after every grocery run and forgetting half the time. Put it on the food and you answer once.

A few rules came with it, the kind you only notice once it's live:

  • The flag only stops the app from guessing a date. If someone typed a date off the can, that date stays. A printed label beats my default.
  • Turning it on clears dates already sitting on shelf items for that food. Otherwise the box is checked and the salt still shows up in the Expiring list, and the checkbox looks broken.
  • Turning it off doesn't bring the old dates back. They were guesses. The undo is a person entering a real date.

Four ways in

Four different places in the app can put a date on a shelf item: adding by hand, scanning a barcode, confirming a photo, and a recheck that runs when you open a container. A flag that works in three of them and leaks in the fourth is worse than no flag, because then you stop trusting the checkbox too.

I'd made exactly that mistake in this app before. The barcode path skipped expiration estimates entirely while the hand-entry path was tested and passing. So now all four paths go through one function, and that function is the only thing allowed to guess a date.

Then I checked that the tests actually test it by deliberately breaking the flag. Seven cases failed across the write paths. If nothing fails when you sabotage a feature, the tests aren't covering it.

Somewhere in your own app or spreadsheet there's probably a column where empty means "unknown" to one person and "doesn't apply" to another. It's worth giving the second meaning its own value, storing it where the fact actually lives, and making sure every path that writes to that column checks it.