<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Waheed Mashwani</title>
    <description>The latest articles on DEV Community by Waheed Mashwani (@waheed_mashwani_840447aa8).</description>
    <link>https://dev.to/waheed_mashwani_840447aa8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4195703%2F5b4e3a1a-6c9e-40b4-acdb-91a802b64b14.jpg</url>
      <title>DEV Community: Waheed Mashwani</title>
      <link>https://dev.to/waheed_mashwani_840447aa8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/waheed_mashwani_840447aa8"/>
    <language>en</language>
    <item>
      <title>Building a Calorie Counter for Dogs: The Vet Math, the Code, and the Food Data</title>
      <dc:creator>Waheed Mashwani</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:35:22 +0000</pubDate>
      <link>https://dev.to/waheed_mashwani_840447aa8/building-a-calorie-counter-for-dogs-the-vet-math-the-code-and-the-food-data-11cc</link>
      <guid>https://dev.to/waheed_mashwani_840447aa8/building-a-calorie-counter-for-dogs-the-vet-math-the-code-and-the-food-data-11cc</guid>
      <description>&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;I run &lt;a href="https://whatdogscaneat.com/" rel="noopener noreferrer"&gt;whatdogscaneat.com&lt;/a&gt;, a site that answers one deceptively simple question: what human foods can dogs actually eat? Every guide on the site ends with the same follow-up from readers — “Okay, it’s safe. But how much can I give?”&lt;/p&gt;

&lt;p&gt;That question is a software problem. “Safe” is a yes/no lookup. “How much” is a calculation, and it depends on the dog’s weight, age, neuter status, and activity level. So I built a calorie counter for dogs: enter the dog, pick the food, get a portion that fits the dog’s daily budget instead of quietly blowing it.&lt;/p&gt;

&lt;p&gt;This post is the full build notes — the veterinary formulas underneath it, a working JavaScript implementation you can lift, the food database I started with, and a worked example you can test your own code against.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem isn’t the math. It’s the model
&lt;/h2&gt;

&lt;p&gt;A first instinct is linear: twice the weight, twice the calories. That’s wrong for dogs, and it’s wrong in a way that matters. Metabolic rate scales with body surface area and metabolically active tissue, not raw mass — which is why veterinary nutrition uses an allometric (power) formula instead of a straight line.&lt;/p&gt;

&lt;p&gt;Get the model wrong and the errors compound at the extremes: you massively overfeed small dogs and underfeed large ones. The good news is the correct model is only three steps, and each step maps cleanly to a function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Resting Energy Requirement (RER)
&lt;/h3&gt;

&lt;p&gt;The Resting Energy Requirement (RER) is the energy a dog needs at complete rest in a comfortable environment — basically, the cost of staying alive: breathing, circulation, cell repair, brain function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formula&lt;/strong&gt;:  RER (kcal/day)  =  70 × (body weight in kg) ^ 0.75&lt;/p&gt;

&lt;p&gt;That 0.75 exponent is the whole trick. In code it’s just Math.pow(weightKg, 0.75), but conceptually it’s what stops a 40 kg dog from being assigned four times the calories of a 10 kg dog. (For reference, the 40 kg dog’s RER is about 2.8× the 10 kg dog’s — not 4×.)&lt;/p&gt;

&lt;p&gt;Two implementation notes before you go further. First, always calculate in kilograms internally and convert at the UI boundary — most owners, especially in the US, think in pounds. Second, use the dog’s ideal weight, not necessarily today’s weight, if the dog is overweight. Feeding an overweight dog for the weight it already is just maintains the problem. More on that in the edge cases below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Daily Energy Requirement (DER) multipliers
&lt;/h3&gt;

&lt;p&gt;Nobody’s dog actually lives at rest. The Daily Energy Requirement (DER) adjusts RER with a multiplier for life stage and lifestyle:&lt;/p&gt;

&lt;p&gt;Formula:  DER (kcal/day)  =  RER ×  life-stage multiplier&lt;/p&gt;

&lt;p&gt;Dog profile&lt;/p&gt;

&lt;p&gt;Multiplier&lt;/p&gt;

&lt;p&gt;Notes&lt;/p&gt;

&lt;p&gt;Weight loss (vet-supervised)&lt;/p&gt;

&lt;p&gt;1.0 × RER&lt;/p&gt;

&lt;p&gt;Calculated on ideal weight&lt;/p&gt;

&lt;p&gt;Inactive / obesity-prone adult&lt;/p&gt;

&lt;p&gt;1.2 – 1.4&lt;/p&gt;

&lt;p&gt;Senior and low-activity dogs&lt;/p&gt;

&lt;p&gt;Neutered adult (typical pet)&lt;/p&gt;

&lt;p&gt;1.6&lt;/p&gt;

&lt;p&gt;The default for most pets&lt;/p&gt;

&lt;p&gt;Intact adult&lt;/p&gt;

&lt;p&gt;1.8&lt;/p&gt;

&lt;p&gt;Higher baseline metabolism&lt;/p&gt;

&lt;p&gt;Light work / very active&lt;/p&gt;

&lt;p&gt;2.0&lt;/p&gt;

&lt;p&gt;Regular long exercise&lt;/p&gt;

&lt;p&gt;Puppy, over 4 months&lt;/p&gt;

&lt;p&gt;2.0&lt;/p&gt;

&lt;p&gt;Growth on top of maintenance&lt;/p&gt;

&lt;p&gt;Puppy, under 4 months&lt;/p&gt;

&lt;p&gt;3.0&lt;/p&gt;

&lt;p&gt;Fastest growth phase&lt;/p&gt;

&lt;p&gt;Gestation (late) / lactation&lt;/p&gt;

&lt;p&gt;up to 3.0+&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Varies widely — vet guidance needed&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Treat these multipliers as starting points, not promises. Two neutered adults of identical weight can maintain on noticeably different intakes, which is why any responsible tool tells the user to monitor body condition over 2–4 weeks and adjust by roughly 10% at a time. In product terms: DER is your default value, not your ground truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — The 10% treat budget rule
&lt;/h3&gt;

&lt;p&gt;Commercial dog food is formulated to be “complete and balanced” — every vitamin and mineral in the right ratio, assuming the dog eats essentially all of its calories from that food. Human-food extras, treats, chews, and training rewards dilute that balance.&lt;/p&gt;

&lt;p&gt;The widely used veterinary rule of thumb is simple: treats and extras should make up no more than 10% of daily calories. The other 90% should come from the complete diet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule&lt;/strong&gt;:  Treat budget (kcal/day)  =  DER ×  0.10&lt;/p&gt;

&lt;p&gt;This is the feature that makes the tool actually useful. “Your dog can have broccoli” is trivia. “Your dog has 116 kcal of treat budget today, and this portion uses 17 of them” is a decision. That second sentence is the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The JavaScript implementation
&lt;/h2&gt;

&lt;p&gt;Here is the whole engine. No dependencies, no framework — the interesting part is the validation and the units, not the arithmetic.&lt;/p&gt;

&lt;p&gt;// dog-calorie-counter.js&lt;/p&gt;

&lt;p&gt;// RER = 70 * (kg ^ 0.75)  |  DER = RER * multiplier&lt;/p&gt;

&lt;p&gt;const DER_MULTIPLIERS = {&lt;/p&gt;

&lt;p&gt;weightLoss:   1.0,&lt;/p&gt;

&lt;p&gt;inactive:     1.4,&lt;/p&gt;

&lt;p&gt;neuteredAdult: 1.6,&lt;/p&gt;

&lt;p&gt;intactAdult:  1.8,&lt;/p&gt;

&lt;p&gt;active:       2.0,&lt;/p&gt;

&lt;p&gt;puppyOlder:   2.0,  // over 4 months&lt;/p&gt;

&lt;p&gt;puppyYoung:   3.0,  // under 4 months&lt;/p&gt;

&lt;p&gt;};&lt;/p&gt;

&lt;p&gt;const LB_TO_KG = 0.45359237;&lt;/p&gt;

&lt;p&gt;function toKg(weight, unit = 'kg') {&lt;/p&gt;

&lt;p&gt;if (unit === 'lb') return weight * LB_TO_KG;&lt;/p&gt;

&lt;p&gt;if (unit === 'kg') return weight;&lt;/p&gt;

&lt;p&gt;throw new Error(&lt;code&gt;Unsupported unit: ${unit}&lt;/code&gt;);&lt;/p&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;function calculateDogCalories({ weight, unit = 'kg',&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                              profile = 'neuteredAdult' }) {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;const weightKg = toKg(weight, unit);&lt;/p&gt;

&lt;p&gt;if (!Number.isFinite(weightKg) || weightKg &amp;lt;= 0) {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;throw new Error('Weight must be a positive number.');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;if (weightKg &amp;lt; 0.5 || weightKg &amp;gt; 100) {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;throw new Error('Weight outside plausible dog range (0.5-100 kg).');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;const multiplier = DER_MULTIPLIERS[profile];&lt;/p&gt;

&lt;p&gt;if (multiplier === undefined) {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;throw new Error(`Unknown profile: ${profile}`);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;const rer = 70 * Math.pow(weightKg, 0.75);&lt;/p&gt;

&lt;p&gt;const der = rer * multiplier;&lt;/p&gt;

&lt;p&gt;return {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;weightKg: round1(weightKg),

rer: Math.round(rer),

der: Math.round(der),

treatBudget: Math.round(der * 0.10), // the 10% rule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;};&lt;/p&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;function caloriesForPortion(food, grams) {&lt;/p&gt;

&lt;p&gt;// food.kcalPer100g comes from the database below&lt;/p&gt;

&lt;p&gt;return Math.round((food.kcalPer100g / 100) * grams);&lt;/p&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;const round1 = (n) =&amp;gt; Math.round(n * 10) / 10;&lt;/p&gt;

&lt;p&gt;// Example: 50 lb neutered adult&lt;/p&gt;

&lt;p&gt;console.log(calculateDogCalories(&lt;/p&gt;

&lt;p&gt;{ weight: 50, unit: 'lb', profile: 'neuteredAdult' }));&lt;/p&gt;

&lt;p&gt;// -&amp;gt; { weightKg: 22.7, rer: 728, der: 1164, treatBudget: 116 }&lt;/p&gt;

&lt;h2&gt;
  
  
  A few deliberate choices in there:
&lt;/h2&gt;

&lt;p&gt;Validate the weight, hard — everything downstream is wrong if this is wrong, so fail loudly instead of returning NaN-flavoured nonsense.&lt;/p&gt;

&lt;p&gt;Round for display, not for storage — owners do not need decimal calories. Whole numbers read as guidance; 1,164.3 kcal reads as false precision.&lt;/p&gt;

&lt;p&gt;Keep multipliers as named data — a string key like neuteredAdult is self-documenting in analytics and survives a UI redesign. A bare 1.6 buried in a component does not.&lt;/p&gt;

&lt;p&gt;Separate the dog from the food — portion math is a separate pure function, so the food database can grow without touching the calorie engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The food database: calories per 100g
&lt;/h2&gt;

&lt;p&gt;The database format is boring on purpose: one record per food, calories per 100 grams, plus a safety flag. Per-100g is the standard unit in nutrition data (it’s how USDA FoodData Central reports values), which makes new foods a one-line addition instead of a conversion exercise.&lt;/p&gt;

&lt;p&gt;Values below are typical raw or plainly cooked values, rounded. Always check the full safety notes for a food before surfacing it to users — calorie-safe is not the same as dog-safe, and preparation (added salt, oil, onion, garlic, xylitol in peanut butter) changes the answer.&lt;/p&gt;

&lt;p&gt;Food (per 100 g)&lt;/p&gt;

&lt;p&gt;kcal&lt;/p&gt;

&lt;p&gt;Budget note&lt;/p&gt;

&lt;p&gt;Pumpkin purée (plain, canned)&lt;/p&gt;

&lt;p&gt;26&lt;/p&gt;

&lt;p&gt;Very budget-friendly&lt;/p&gt;

&lt;p&gt;Green beans (raw)&lt;/p&gt;

&lt;p&gt;31&lt;/p&gt;

&lt;p&gt;Very budget-friendly&lt;/p&gt;

&lt;p&gt;Broccoli (raw)&lt;/p&gt;

&lt;p&gt;34&lt;/p&gt;

&lt;p&gt;See worked notes below&lt;/p&gt;

&lt;p&gt;Carrots (raw)&lt;/p&gt;

&lt;p&gt;41&lt;/p&gt;

&lt;p&gt;Crunchy, low cost&lt;/p&gt;

&lt;p&gt;Apple (raw, no seeds/core)&lt;/p&gt;

&lt;p&gt;52&lt;/p&gt;

&lt;p&gt;Remove seeds and core&lt;/p&gt;

&lt;p&gt;Sweet potato (raw)&lt;/p&gt;

&lt;p&gt;86&lt;/p&gt;

&lt;p&gt;Usually served cooked&lt;/p&gt;

&lt;p&gt;Banana&lt;/p&gt;

&lt;p&gt;89&lt;/p&gt;

&lt;p&gt;Sugary — small portions&lt;/p&gt;

&lt;p&gt;White rice (cooked)&lt;/p&gt;

&lt;p&gt;130&lt;/p&gt;

&lt;p&gt;Bland-diet staple&lt;/p&gt;

&lt;p&gt;Chicken breast (cooked, skinless)&lt;/p&gt;

&lt;p&gt;165&lt;/p&gt;

&lt;p&gt;Lean protein&lt;/p&gt;

&lt;p&gt;Cheddar cheese&lt;/p&gt;

&lt;p&gt;403&lt;/p&gt;

&lt;p&gt;Tiny cubes only&lt;/p&gt;

&lt;p&gt;Peanut butter (unsweetened)&lt;/p&gt;

&lt;p&gt;588&lt;/p&gt;

&lt;h2&gt;
  
  
  Highest cost — check for xylitol
&lt;/h2&gt;

&lt;p&gt;Broccoli deserves a callout because it’s the food readers ask about most. It’s low-calorie (34 kcal per 100 g) and generally safe in small amounts, but the florets contain isothiocyanates that can cause gastric irritation if a dog eats a lot of it — another reason portion math matters more than a bare yes/no. The full breakdown, including stem vs. floret and raw vs. cooked, is in the site’s guide: can dogs eat broccoli?.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example — a 50 lb dog, end to end
&lt;/h2&gt;

&lt;p&gt;If you implement this yourself, use these numbers as your unit test. A 50 lb dog is a good test case: common size, and it forces the pound-to-kilogram conversion.&lt;/p&gt;

&lt;p&gt;Step&lt;/p&gt;

&lt;p&gt;Calculation&lt;/p&gt;

&lt;p&gt;Result&lt;/p&gt;

&lt;p&gt;Convert weight&lt;/p&gt;

&lt;p&gt;50 lb × 0.4536&lt;/p&gt;

&lt;p&gt;22.68 kg&lt;/p&gt;

&lt;p&gt;RER&lt;/p&gt;

&lt;p&gt;70 × 22.68 ^ 0.75&lt;/p&gt;

&lt;p&gt;728 kcal/day&lt;/p&gt;

&lt;p&gt;DER (neutered adult, ×1.6)&lt;/p&gt;

&lt;p&gt;728 × 1.6&lt;/p&gt;

&lt;p&gt;1,164 kcal/day&lt;/p&gt;

&lt;p&gt;Treat budget (10%)&lt;/p&gt;

&lt;p&gt;1,164 × 0.10&lt;/p&gt;

&lt;p&gt;116 kcal/day&lt;/p&gt;

&lt;p&gt;Main food allowance (90%)&lt;/p&gt;

&lt;p&gt;1,164 × 0.90&lt;/p&gt;

&lt;p&gt;1,048 kcal/day&lt;/p&gt;

&lt;p&gt;Now spend that 116 kcal budget three different ways, using the database above:&lt;/p&gt;

&lt;p&gt;50 g of broccoli = 17 kcal — about 15% of the day’s treat budget. Plenty of room left — this is what a “cheap” treat looks like.&lt;/p&gt;

&lt;p&gt;1 tablespoon of peanut butter (about 16 g) = 94 kcal — over 80% of the budget in a single spoonful. Calorie-dense foods are where owners accidentally overfeed.&lt;/p&gt;

&lt;p&gt;30 g of cheddar = 121 kcal — more than the entire treat budget, before a single training treat. Cheese is a garnish, not a snack.&lt;/p&gt;

&lt;p&gt;That contrast is the insight the tool exists to deliver. The foods themselves are all “safe in moderation” — moderation just turns out to mean wildly different gram amounts, and nobody can eyeball the difference between 17 kcal and 121 kcal on a plate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge cases developers miss
&lt;/h2&gt;

&lt;p&gt;Ideal weight vs. current weight — for weight-loss and weight-gain profiles, calculate DER from the target weight and say so in the UI. Silently using current weight produces a tool that tells an overweight dog to stay overweight.&lt;/p&gt;

&lt;p&gt;Medical cases are out of scope — one multiplier table is a starting point. Puppies, pregnant and nursing dogs, working dogs, and dogs with kidney, pancreatic, or endocrine conditions need a veterinarian’s numbers, not an app’s. Put that disclaimer next to the result, not in a footer nobody reads.&lt;/p&gt;

&lt;p&gt;Unit confusion is your #1 bug — a Great Dane puppy and a senior Chihuahua both hit this code. Plausible-range validation (I used 0.5–100 kg) catches the classic fat-finger errors: 500 instead of 50, kilograms entered into a pounds field.&lt;/p&gt;

&lt;p&gt;Food data is approximate — calorie counts for the same food vary by source, ripeness, and cooking method. Show “about” numbers, cite your source in the data file, and version the database so a value change doesn’t silently rewrite a user’s history.&lt;/p&gt;

&lt;p&gt;Toxic beats low-calorie, every time — some human foods — grapes, raisins, onions, garlic, macadamia nuts, chocolate, and anything sweetened with xylitol — are toxic at any portion. The safety flag must veto the food before the calorie math ever runs. Never let a portion calculator imply a safe amount of a toxic food.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would build next
&lt;/h2&gt;

&lt;p&gt;A daily log — so a day’s extras accumulate against the budget instead of each one being judged in isolation.&lt;/p&gt;

&lt;p&gt;Ideal-weight mode — body condition score (a simple 1–9 visual scale) mapped to a suggested ideal-weight range, so the ideal-weight calculation isn’t a guess.&lt;/p&gt;

&lt;p&gt;One database, everywhere — the same per-100g record drives the counter, the individual food guides, and a printable “fridge chart” — one source of truth, three surfaces.&lt;/p&gt;

&lt;p&gt;If you want to see the food side of this in production, the guides on the site pair each food’s safety verdict with exactly this kind of portion thinking — starting with the homepage and working through foods one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;A dog calorie counter is about 40 lines of JavaScript. The work isn’t the code — it’s choosing the right model (allometric, not linear), the right defaults (named multipliers, ideal weight), and the right guardrails (the 10% budget, toxic-food vetoes, honest rounding).&lt;/p&gt;

&lt;p&gt;Build those in, and you’ve got a tool that answers the question owners actually ask. Not “can my dog eat this?” — but “how much of this can my dog eat, today, without it costing tomorrow?”&lt;/p&gt;

&lt;p&gt;Please note:  This article explains the standard formulas used in veterinary nutrition for educational purposes. It isn’t a substitute for advice from your own veterinarian, who knows your dog’s health history. If your dog has a medical condition, is pregnant or nursing, or needs to lose significant weight, get a feeding plan from your vet.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
