Years ago, a cooking blog got me thinking about reverse-engineering recipes. I
thought I could build an application for that. For those of us privileged
enough to contribute, open source software is an endless time sync. I kept
putting off the idea for some other shiny pet project–usually
With my work on JSHint finally maybe wrapping up, and with a whole lot of extra
time due to coronavirus-inspired lifestyle changes, I finally set out to build
the thing in March. Today, it’s done. It’s called “Ingradient,” and you can
find it at ingradient.com.
To cap things off, I thought I’d write a bit about what I built and what I
What is it?
Ingradient is an application designed to help you recreate food products in
your kitchen by estimating the ingredient measures. Pick a product you’d like
to make, dial in the nutrition facts along with the ingredients, and Ingradient
will spit out measurements for each ingredient.
How does it work?
Ingradient works by combining the information you provide with nutrition data
published by the US Department of Agriculture. It
creates a system of equations that relates the nutrition information of the
ingredients with that of the final product. By solving for the variables in
that system, it determines the mass of each ingredient.
So, let’s say you want to make a granola bar. Not just any granola bar, but a
Nature Valley™ Peanut Butter Crunchy Granola
You know you’ll need oats, sugar, canola oil, and peanut butter. General Mills
also uses rice flour, brown sugar syrup, and–of course–soy lecithin, but
you’re happy to skip that stuff. But still, you don’t know how much of each
ingredient you need.
…but you do know a bit about its nutrients. One such bar has 190 calories,
8 grams of fat, 2 grams of fiber, and 4 grams of protein. You put all that into
Ingradient, and it looks up the amount of calories, fat, fiber, and protein in
each ingredient. It builds a system of equations from all that data:
3.79×Moats + 3.87×Msugar + 8.84×Moil + 5.98×MPB = 190 calories
0.07×Moats + 0.00×Msugar + 1.00×Moil + 0.51×MPB = 8 grams of fat
0.10×Moats + 0.00×Msugar + 0.00×Moil + 0.05×MPB = 2 grams of fiber
0.13×Moats + 0.00×Msugar + 0.00×Moil + 0.22×MPB = 4 grams of protein
With four variables and four equations, there is exactly one possible value for
each variable. Ingradient uses Gaussian
elimination to find the
values and reports them back to you:
Moats = 15 grams
Msugar = 15 grams
Moil = 2 grams
MPB = 9 grams
How doesn’t it work?
It’s not perfect, though, not by a long shot. This approach only works if you
can provide a nutrient measure (e.g. carbohydrates, saturated fat, etc.) for
every ingredient. For products with a bunch of ingredients, you may not have
enough nutrients. Even before you run out of nutrients, you’ll find that some
of them (e.g. sodium and cholesterol) are present in trace amounts, making them
weak signals for inferring measurements.
In the application’s guide, I encourage users to stick with macronutrients and
get creative with ingredient simplification. For example, a product that has
sugar, corn syrup, and high-fructose corn syrup can probably be approximated
with sugar alone (most cooks aren’t likely to have HFCS on hand, anyway).
The most jarring problem, though, is that the application sometimes provides
incoherent answers. Want to know how to make a Carrot Cake Larabar? Ingradient
will tell you to use 54 grams of dates, 54 grams of almonds, and -29 grams of
walnuts. While that solution makes sense on paper, it’s useless in the kitchen.
It’s hard to predict when you’ll get an answer like this, but the further your
input strays from the actual content, the more likely you are to get gibberish.
The application was “just for fun,” and I’m hoping folks have the same
expectations for its output.
Origin of the idea
In 2010, J. Kenji López-Alt wrote an article
titled The Ins-n-Outs of an In-N-Out Double-Double,
In it, he used stoichiometry to
reverse-engineer the contents of a sandwich dressing based on the ingredients
and nutrition data provided by the restaurant that sold it.
This always seemed like a useful application of the chemistry process–one that
could generalize to all sorts of other cooking experiments. It’s just so
labor-intensive! I thought I could teach a computer to do it for me.
Although I originally envisioned a command-line tool that would never see the
light of day, I eventually decided to make this a full-fledged web application.
Doing so would allow my work to benefit someone else out there, give me an
opportunity to brush up on some new technologies, and let me proselytize for
free software a bit.
I’m a web developer by trade, but my work in recent years has been focused more
on web standards and less on soup-to-nuts application development. That made
this project an opportunity to study more practical topics. For instance, even
though I’ve collaborated with browser developers to implement Service
I haven’t had much of an opportunity to use them. I finally got to experiment
with Service Workers a bit, and in “progressive web app” fashion, Ingradient is
offline-enabled. Building a search interface over a large dataset like the
USDA’s nutrition data also gave me an opportunity to work with
The front-end tooling ecosystem has grown a ton in recent years, and when
saying something. Building an application of my own design let me branch out
and play with a bunch of new-to-me toys. That started with an initial
experiment with Cycle.js. While I appreciate the
concept, when it comes to development ergonomics, it doesn’t hold a candle to
React. Trouble is, React’s developed by Facebook, and I
really can’t abide by that company for much of anything. This makes me
especially thankful for Preact which offers the same
development experience with none of the ethical baggage. Along those same
lines, JSX is a non-standard
programming language that’s always left a bad taste in my mouth. I’ve had the
htm project bookmarked for years, and I was
this early experiment gluing together Cycle.js and
Okay, okay. I’ll get off my soapbox, now.
The most important lessons were about my personal shortsightedness. Even though
I intentionally set out to make an application for use by others, my initial
prototype was inscrutable. I’d been thinking about this project for years, so
naturally, I knew how to use it. A few user tests made it clear that this was
not intuitive and that folks would need a few hints to understand what they
were looking at. It’s been years since I formally studied human-computer
interaction, and my lack of practice was obvious.
Honestly, I’m a bit disappointed by the ways Ingradient fails. It’s mostly the
negative measurements that are getting me down; that seems like the biggest
detriment to the tool’s usefulness. I’m still wondering if I can avoid those
impossible solutions. If anyone out there has ideas (maybe an algorithm to
nudge the input until it can be solved with practical values), then I’m all
That said, the application proves the concept, and it usually gives a good
starting point for home-made recreations. That’s why I’m calling it a day. It’s
rare for me to have a definitive end to a side-project, so this finality is
Anyway, here’s hoping Ingradient helps you build a few recipes of your own!