Battery Life Estimation: From mAh to Real-World Runtime
An ESP32 sensor node with a 2,000 mAh lithium cell should last 12.5 hours at 160 mA continuous draw. Simple division: 2,000 / 160 = 12.5. But flash the radio for 2 seconds every minute and sleep the rest, and that same battery stretches past two weeks. The difference between 12.5 hours and 15+ days comes down to duty cycle math that most datasheets never spell out.
Battery life estimation sounds trivial. Divide capacity by current, get hours. In practice, six factors conspire to make the naive answer wrong: duty cycle, depth of discharge, self-discharge, the Peukert effect, temperature, and battery aging. This guide walks through each one with numbers.
The Naive Formula
The starting point for any battery life calculation:
Runtime (hours) = Capacity (mAh) / Load current (mA)
A 3,000 mAh phone battery powering a 500 mA average load: 3,000 / 500 = 6 hours. That matches real-world experience for continuous screen-on use, which is why the formula feels right.
It breaks down the moment any of these assumptions fail:
- The load is not constant (it almost never is)
- The battery cannot be drained to 0% (chemistry-dependent cutoffs apply)
- High current draws reduce effective capacity (Peukert effect)
- Cold temperatures shrink available capacity
- Self-discharge eats charge even with no load
The naive formula is the ceiling. Every real-world factor pushes runtime lower.
mAh vs Wh: The Comparison Trap
Milliamp-hours (mAh) measure electric charge. Watt-hours (Wh) measure energy. They are not interchangeable unless you specify the voltage.
Wh = mAh x V / 1000
A 3,000 mAh lithium cell at 3.7 V stores 11.1 Wh of energy. A 3,000 mAh NiMH AA cell at 1.2 V stores only 3.6 Wh. Same mAh, one-third the energy. Marketing departments love mAh because the number is bigger, but energy content is what determines runtime when comparing across voltages.
This matters most when comparing:
- Lithium-ion (3.7 V) vs NiMH (1.2 V): A 2,000 mAh Li-ion pack replaces three series NiMH cells at 2,000 mAh. The Li-ion stores 7.4 Wh; the NiMH pack stores 7.2 Wh (3 x 1.2 V x 2,000 mAh / 1000). Roughly equivalent energy despite identical mAh ratings, but only because the NiMH cells are in series tripling the voltage.
- Power banks: A "20,000 mAh" power bank at its internal 3.7 V cell voltage stores 74 Wh. Charging a phone at 5 V USB, the usable capacity drops to about 14,800 mAh (74 Wh / 5 V x 1000) before accounting for conversion losses. At ~90% converter efficiency, expect ~13,300 mAh at 5 V output.
The battery life calculator accepts both mAh and Wh inputs and converts between them using the nominal voltage you specify.
Duty Cycle: Where the Real Gains Live
Most battery-powered devices alternate between active and sleep states. The average current, not the peak current, determines battery life:
I_avg = (I_active x t_active + I_sleep x t_sleep) / (t_active + t_sleep)
Worked example: LoRa environmental sensor
An ESP32 with a BME280 sensor and LoRa radio. It wakes every 5 minutes, reads the sensor, transmits a packet, and goes back to deep sleep.
| Mode | Current | Duration |
|---|---|---|
| Active (sensor read + LoRa TX) | 120 mA | 3 seconds |
| Deep sleep | 10 uA (0.01 mA) | 297 seconds |
I_avg = (120 x 3 + 0.01 x 297) / 300
I_avg = (360 + 2.97) / 300
I_avg = 1.21 mA
With a 3,000 mAh LiPo cell:
- Naive (continuous active): 3,000 / 120 = 25 hours
- With duty cycle: 3,000 / 1.21 = 2,479 hours = 103 days
The 99:1 sleep-to-active ratio transforms a one-day battery into a three-month one. This is why IoT power budgeting obsesses over sleep current. The 10 uA sleep current contributes 2.97 mA-seconds per cycle, which is less than 1% of the per-cycle energy. But if deep sleep current were 1 mA instead of 10 uA (a common mistake when forgetting to disable unused peripherals), I_avg jumps to 2.19 mA, cutting runtime from 103 days to 57.
Verify your duty cycle numbers with the battery life calculator.
The Peukert Effect: Lead-Acid's Hidden Tax
In 1897, German scientist Wilhelm Peukert documented that lead-acid batteries deliver less total charge at higher discharge rates. A 100 Ah battery rated at the C/20 rate (5 A for 20 hours) might deliver only 87 Ah when discharged at 10 A.
The relationship follows a power law:
t = H x (C / (I x H))^k
Where:
- t = actual runtime in hours
- H = rated discharge time (hours), typically 20 for lead-acid
- C = rated capacity (Ah) at the H-hour rate
- I = actual discharge current (A)
- k = Peukert exponent (dimensionless, always >= 1)
Typical Peukert exponents by chemistry:
| Chemistry | Peukert exponent (k) | Impact |
|---|---|---|
| Lithium-ion / LiFePO4 | 1.02 to 1.05 | Negligible; safe to ignore for most applications |
| VRLA / AGM lead-acid | 1.05 to 1.15 | Moderate; matters above C/5 rates |
| Gel lead-acid | 1.10 to 1.25 | Noticeable at higher currents |
| Flooded lead-acid | 1.20 to 1.40 | Significant; always compute |
Worked example: golf cart battery
A 225 Ah flooded lead-acid battery (rated at C/20 = 11.25 A, k = 1.25) powering a motor drawing 50 A:
t = 20 x (225 / (50 x 20))^1.25
t = 20 x (225 / 1000)^1.25
t = 20 x (0.225)^1.25
t = 20 x 0.155
t = 3.10 hours
The naive calculation gives 225 / 50 = 4.5 hours. The Peukert correction reveals you actually get 3.10 hours, a 31% reduction. At the rated 11.25 A, the formula returns the full 20 hours. The penalty grows sharply with discharge rate.
For lithium cells with k near 1.02, the capacity penalty at high discharge rates is small compared to lead-acid. A 100 Ah lithium pack discharged at 10x its C/20 rate still delivers over 95% of its rated capacity. The Peukert effect exists but rarely changes the design decision, which is one reason lithium dominates high-drain applications.
The CalcFlux battery life calculator includes an optional Peukert exponent field. Set it to 1.0 (the default) for lithium, or enter your battery's k value for lead-acid.
The Derating Stack
Five factors reduce real-world runtime below the theoretical maximum. Apply them multiplicatively:
Runtime_real = Runtime_naive x DoD x (1 - self_discharge) x temp_factor x age_factor
Depth of Discharge (DoD)
No battery chemistry tolerates 100% discharge without damage.
| Chemistry | Recommended max DoD | Usable fraction |
|---|---|---|
| Lead-acid (deep cycle) | 50% | 0.50 |
| Lead-acid (starter) | 20% | 0.20 |
| Li-ion (with BMS) | 80 to 90% | 0.80 to 0.90 |
| LiFePO4 | 80% | 0.80 |
| Alkaline (primary) | ~95% | 0.95 |
Deeper discharge shortens cycle life dramatically. A lead-acid battery at 50% DoD may achieve 1,000 charge cycles; at 80% DoD, roughly 500 cycles (halved). LiFePO4 cells operated between 15% and 85% SoC see substantially longer cycle life than cells routinely drained to 0%.
Self-Discharge
Every battery loses charge while sitting idle.
| Chemistry | Self-discharge rate |
|---|---|
| Alkaline | 3 to 5% per year at 20 C (premium cells); up to 20% for budget cells |
| NiMH (standard) | 15 to 30% per month |
| NiMH (low self-discharge, e.g. Eneloop) | 2 to 3% per month; ~70 to 85% retained after 1 year at 20 C |
| Li-ion | 2 to 3% per month at 20 C |
| LiFePO4 | 2 to 3% per month |
| NiCd | ~10% first 24 hours, then ~10% per month |
Temperature accelerates self-discharge significantly. A 1999 study on early Li-ion cells stored at 100% SoC measured 8% per month at 21 C and 31% per month at 60 C. Modern cells at partial charge are better (the 2-3% figure above), but the temperature relationship holds: expect self-discharge to roughly double for every 10 C rise. For devices stored in hot environments (vehicle dashboards, outdoor enclosures in summer), self-discharge can rival the load current.
Self-discharge matters for long-duration deployments. An IoT sensor sleeping most of the time at 1 mA average on a 3,000 mAh cell might theoretically last 125 days. But 3% monthly self-discharge consumes ~90 mAh per month, equivalent to ~0.125 mA constant drain. That is 12.5% of the 1 mA load, reducing runtime to about 111 days.
Temperature Derating
Cold batteries deliver less capacity because internal chemical reactions slow and internal resistance rises.
| Temperature | Approximate capacity vs. 25 C |
|---|---|
| 25 C | 100% (reference) |
| 0 C | 70 to 80% (Li-ion), 50% (alkaline at 250 mA, per manufacturer data) |
| -10 C | 60 to 70% (Li-ion) |
| -20 C | 50 to 60% (Li-ion) |
Lead-acid loses approximately 10% of capacity per 10 C drop below 25 C, a widely used rule of thumb from IEEE Std 485 battery sizing guidance.
For outdoor sensors in northern climates, winter temperatures can cut effective capacity in half. Design for the coldest expected operating temperature, not room temperature.
Battery Aging
Li-ion cells lose 2 to 3% capacity per year even when stored at moderate charge levels (40 to 60% SoC) and 20 C. Active cycling adds to this. After 500 full cycles, a typical Li-ion cell retains 80% of its original capacity (the industry-standard end-of-life threshold). For a device expected to last 3 years on one charge cycle per day, factor in at least a 20% capacity reduction by end of life.
Putting It All Together: Worked Example
Scenario: A LoRa temperature sensor deployed outdoors in the northeastern US. Operating temperature range: -10 C winter to 35 C summer. 3,000 mAh 3.7 V LiPo cell. Duty cycle from the earlier example: I_avg = 1.21 mA.
Step 1. Naive runtime: 3,000 / 1.21 = 2,479 hours (103 days).
Step 2. Apply DoD (85% usable for Li-ion with BMS): 2,479 x 0.85 = 2,107 hours (88 days).
Step 3. Apply self-discharge (3% per month over ~3 months): roughly 9% total loss. 2,107 x 0.91 = 1,917 hours (80 days).
Step 4. Apply winter temperature derating (70% capacity at -10 C worst case). For a year-round deployment, use an average derating of 85%: 1,917 x 0.85 = 1,630 hours (68 days).
Step 5. Realistic runtime: ~68 days. The naive formula predicted 103. That is a 34% reduction from the stack of real-world factors.
For a 12-month deployment target, you would need a battery roughly 5.3x larger: 16,000 mAh. Or redesign the duty cycle to reduce I_avg. Extending the sleep interval from 5 minutes to 15 minutes drops I_avg to 0.41 mA, pushing the derated runtime past 200 days on the original 3,000 mAh cell.
Common Mistakes
Using mAh to compare batteries at different voltages. A 10,000 mAh USB power bank (3.7 V internal) stores 37 Wh. A 10,000 mAh 12 V lead-acid battery stores 120 Wh. The lead-acid pack holds over 3x the energy despite the same mAh headline number. Always compare in Wh.
Ignoring sleep current. An ESP32 module with WiFi disabled but the radio modem still powered draws 20 mA, not the 10 uA deep-sleep figure from the datasheet. Unused peripherals (GPS modules, LoRa radios, sensors) often have their own quiescent current that persists unless explicitly powered down via a MOSFET switch or regulator enable pin. Measure sleep current with a uA-resolution meter before trusting datasheet figures.
Treating rated capacity as usable capacity. A "100 Ah" lead-acid deep-cycle battery usable to 50% DoD provides 50 Ah. Designing for 100 Ah will either damage the battery or trigger the low-voltage cutoff well before the expected runtime.
Forgetting the voltage regulator. A 3.7 V LiPo feeding a 3.3 V LDO regulator wastes (3.7 - 3.3) / 3.7 = 10.8% of battery energy as heat in the regulator. A switching regulator at 90 to 95% efficiency reclaims most of that loss. Over a multi-month deployment, regulator efficiency compounds into weeks of difference.
Assuming linear discharge. Battery voltage drops as the cell discharges. A load that draws constant power (not constant current) draws increasing current as voltage sags, accelerating the discharge curve. Size for the highest current at the lowest expected voltage, not the nominal midpoint.
Use the CalcFlux battery life calculator to run these numbers, and the electrical power calculator to convert between watts, volts, and amps for your power budget.