CookSnap is coming soon — Join the waitlist →
CookSnap Journal

How CookSnap Matches Your Fridge to a Real Recipe in Under 200 Milliseconds

· 6 min read · by Alex Vakser
How CookSnap Matches Your Fridge to a Real Recipe in Under 200 Milliseconds

Type your ingredients into the CookSnap recipe finder and watch the result. The full match — canonical ingredient normalization, library lookup, fit-percentage ranking, hero image hydration — completes in 150 to 220 milliseconds on a typical home connection. Generative cooking apps take 8 to 20 seconds for the same query.

The difference matters more than it sounds. Twelve seconds of latency is the gap between “a tool I trust” and “a tool I’m waiting on.” This post is a brief engineering tour of how we got there.

The honest budget

A 200ms total response means the server-side work has to fit in roughly 100ms (the rest is network round-trip and client-side hydration). The 100ms server budget splits like this:

  • Ingredient normalization: 3-7ms
  • Canonical combo lookup & cache hit: 5-12ms
  • Match-scoring against the library: 30-45ms
  • Result ranking & teaser selection: 8-15ms
  • JSON serialization and Postgres return overhead: 15-20ms

Total: ~100ms server, ~50-100ms network. Comfortably under the perceptual threshold for “instant.”

Why retrieval is so much faster than generation

A generative LLM produces output one token at a time. A 300-word recipe is roughly 600 tokens. At 50 tokens per second — a reasonable cloud-API throughput — that’s 12 seconds of wall-clock generation. You can speed it up with speculative decoding, smaller models, batch tricks. You can’t get under about 4 seconds for full-recipe output without compromising quality.

Retrieval is fundamentally different. There’s nothing to generate — the recipe already exists. The work is finding the right one. With a well-indexed library, that’s a constant-time hash lookup plus a sub-linear scan over candidate matches. The library can grow 10x and the query latency barely moves.

The architecture that gets us there

Four layers, in order of where time goes:

  1. Canonical ingredient taxonomy.User input (“roma tomatoes,” “Tomatoes (canned)”, “tom tomato”) gets mapped to a canonical identifier (“tomato”) via a precomputed trigram index. This is the step that lets the matcher operate on a fixed alphabet of about 1,400 canonical ingredients.
  2. Combo cache.Common ingredient combinations (“chicken, rice, broccoli”) have their match results pre-computed and cached. About 78% of real-world queries hit this cache, returning in under 20ms total.
  3. Per-recipe canonical ingredient vectors.Every recipe in the library has a precomputed list of its canonical ingredients with weights (essential vs. garnish vs. optional). The matcher does set-overlap math with weights, not full-text comparison.
  4. Postgres with right indexes. The whole backend is Postgres. No specialized vector DB, no Elasticsearch. The combination of well-shaped indexes, generated columns, and a stored-procedure search RPC is sufficient. Boring tech.

The trade-off nobody asks about

The performance comes from precomputation, which means we pay a cost on the writeside. Adding a new recipe to the library triggers a canonical-ingredient extraction, a weight assignment, a hero-image generation step, and an invalidation of any combo cache entries that intersect with the new recipe’s ingredient set.

For our growth rate (200-400 new recipes per week), this is fine. For a crawl-the-open-web competitor, it would be prohibitive. The architecture only works at curated-library scale.

What we’d change if we started over

Three things in retrospect:

  • We over-engineered the trigram index. A simpler edit-distance fallback for unmatched ingredients would have hit 90% of the same recall at 20% of the complexity.
  • We should have built the combo cache as the foundation, not as an optimization. The current architecture treats it as a performance layer; in retrospect, it’s the entire product.
  • We use Postgres for everything because the team knows it. A search-specialized engine would be slightly faster. We’ve never regretted the choice.

The user-facing point

None of this matters unless it shows up in the experience. The way it shows up is subtle — you type, you tap search, the result is just there. There’s no loading spinner that lingers, no “thinking” animation, no progress bar that ticks while a model streams. The interaction feels like a fact-lookup, because it is one.

We think this is the right shape for the category. Recipes are facts about what exists in a library, not creative outputs to be generated on demand. Building the product around that conviction is what gets you the 200-millisecond ceiling.

Try it yourself: open the recipe finder with the network tab open in your browser. The XHR call to the matcher will return in a couple hundred milliseconds. It’s a small detail. Small details are what people feel.

CookSnap matches the ingredients you already have to real recipes — no AI-generated meals, no substitutions guesswork. Try the free recipe finder.