DEV Community

Javeed Shaik
Javeed Shaik

Posted on

Three things Indian finance code gets wrong (with the numbers)

I maintain a set of finance calculators for India. Every formula ships with a unit test, and the
build fails if a number drifts more than 0.5% from a verified value. That sounds like overkill for
arithmetic until you find out how much published finance code is quietly wrong.

Here are three errors I keep running into, with the actual figures. All three are asserted by tests
in indian-finance-formulas, a
zero-dependency MIT package I extracted from the site.

npm install indian-finance-formulas
Enter fullscreen mode Exit fullscreen mode

1. GST interest is charged on the cash ledger, not the gross bill

This is the most common one, and it is expensive.

Rule 88B(1) of the CGST Rules charges interest on the tax actually debited from your electronic
cash ledger
— not on your gross output liability. If part of your liability was settled through
input tax credit, that part does not attract interest.

Plenty of code runs the interest on the whole output tax. The difference is not subtle:

import { gstInterest } from "indian-finance-formulas";

gstInterest(30000, 30);    // 443.84  — correct: ₹30,000 actually paid in cash
gstInterest(100000, 30);   // 1479.45 — the common error: gross liability
Enter fullscreen mode Exit fullscreen mode

3.33× too high. On a real filing that is the difference between a rounding error and a number
you would argue about.

The fix is a naming discipline as much as a maths one. The parameter is called cashTaxPaid, not
taxAmount, so it is hard to pass the wrong thing without noticing.


2. A step-up SIP does not beat a flat SIP of the same total outlay

Every step-up SIP calculator I have seen shows the step-up winning. It does win — but only because
more money goes in. That is not a strategy insight, it is arithmetic about deposits.

Hold the money constant instead of the starting instalment, and it reverses:

import { stepUpSip, sipFutureValue } from "indian-finance-formulas";

const step = stepUpSip(10000, 12, 20, 10);
// ₹1,98,88,715 on ₹68,73,000 invested — start ₹10k/mo, +10% a year, 12% p.a., 20 years

const flat = sipFutureValue(step.invested / 240, 12, 240);
// ₹2,86,13,098 on the SAME ₹68,73,000
Enter fullscreen mode Exit fullscreen mode

The flat schedule finishes ₹87 lakh ahead on identical money. The reason is simple once you see
it: in a step-up plan, most of the rupees arrive late and compound for fewer years. A flat plan
front-loads the same total, so every rupee gets longer in the market.

Step-up SIPs are still a perfectly good idea — they match rising income, and most people genuinely
cannot invest the flat-equivalent amount in year one. But the reason to use one is behavioural,
not mathematical
, and calculators that imply otherwise are selling a free lunch.

This one is a test, not a comment, because it is exactly the kind of thing a well-meaning
"optimisation" would silently break:

assert(flat > step.futureValue);
Enter fullscreen mode Exit fullscreen mode

3. The Section 87A rebate is a cliff, and needs marginal relief

Under India's new tax regime, a rebate takes your tax to zero up to ₹12,00,000 of taxable income.
Implement that naively and earning one rupee more costs you far more than one rupee — the classic
cliff.

Marginal relief caps the tax at the amount of income above the threshold:

import { incomeTaxNewRegime } from "indian-finance-formulas";

incomeTaxNewRegime(1200000).total;   // 0
incomeTaxNewRegime(1200100).total;   // 104     — ₹100 over the line, ₹104 of tax (incl. 4% cess)
incomeTaxNewRegime(1210000).total;   // 10,400
incomeTaxNewRegime(1250000).total;   // 52,000
Enter fullscreen mode Exit fullscreen mode

Without relief, the ₹12,00,100 case would jump to tens of thousands. Also asserted by a test.


The bit that generalises: statutory values are parameters, not constants

Every function that depends on a notified figure takes it as an argument with a documented default:

gratuity(500000, 30);                        // ₹20,00,000 ceiling — most employees
gratuity(500000, 30, { ceiling: 2500000 });  // ₹25,00,000 — Central Govt civil employees

incomeTaxNewRegime(1600000);                 // FY 2026-27 slabs by default
incomeTaxNewRegime(1600000, { slabs: [...] }) // yours, the day they change
Enter fullscreen mode Exit fullscreen mode

Hard-coding a slab table means waiting for a release when the Finance Act moves. Making it an
argument means the person who noticed can fix it that afternoon.


And the failure mode tests cannot catch

Unit tests protect the arithmetic. They cannot protect the inputs.

I hit a clean example of this today. I was building a table of India's 7th Pay Commission pay
matrix and went to the primary source — the Commission's own report — rather than trusting my
notes. It lists Level 13 entry pay as ₹1,18,500.

That figure is wrong today. The CCS (Revised Pay) (Amendment) Rules, 2017 re-based Level 13 and the
correct figure is ₹1,23,100. The number printed in the primary source has been superseded for
years.

No test catches that. The arithmetic stays perfectly correct while the answer is wrong, and
"I checked the official source" is exactly the reasoning that gets you there. The only defence is
citing and dating each statutory figure, so a human can re-check what a machine cannot.

That is why the calculators carry a source list and a date rather than a vague "updated recently".


Links

Run the suite with npm test — plain node --test, no framework. The three traps above are in
there deliberately, so a regression that reintroduces one fails loudly.

If you have hit a fourth one, I would genuinely like to hear it.

Top comments (0)