Oct 14, 2026 · 4 min read
Taking loan prequalification from 20 seconds to 100 milliseconds
Matchmo's bank prequalification ran on a slow third-party system for two years. I replaced it with our own small, config-driven engine.
Bank prequalification is one of the main things Matchmo does. A homebuyer enters a few numbers: the price of the property, how much they want to borrow, the tenure, their income, their age, how they are employed. Matchmo analyses that profile and matches them to the banks with the best rates for them.
For two years, that step ran on software we did not own. It took about 20 seconds. Today it takes about 100 milliseconds, which is the 200× on my résumé. This is why we replaced it and how the replacement works.
A dependency we could not predict
The prequalification algorithm lived inside a third-party platform, and our own CRM was integrated with it. That worked, until it did not work well.
The other side was slow, and we could not predict how it would behave. Sometimes a request errored, and sometimes the same request did not. We could not fix it, because the code was not ours.
That mattered, because prequalification is one of Matchmo's major products, and it depended on a system that was slow and sometimes failed.
The decision: host our own
I built our own engine, with four goals:
- Control. When something is wrong, the fix is ours to make.
- Predictable output. The same profile gives the same answer, every time.
- Faster integration. One small service our apps call directly.
- Dynamic bank reconfiguration. When a bank changes what it wants, we change it ourselves.
That service is prequal-engine, and it is what runs today.
One question, many banks
Matchmo works with 16+ partner banks, and each bank answers the same question differently. So the engine takes one borrower profile, runs it past every bank, and returns the banks that pass, with the reason for each one that does not.
A bank is data, not code
The central idea: a bank is a configuration entry, not a function. What a bank wants lives in data the engine reads, and the engine itself stays the same for every bank.
This is what makes dynamic reconfiguration real. Changing what a bank wants this month is an edit to data. There is no new code path to write and no new place for a bug to hide.
It also explains the speed. Working out which banks a profile passes is arithmetic and comparisons over configuration held in memory. The calculation itself makes no database or network calls, so there is nothing slow to wait on and nothing unpredictable to wait for.
Two endpoints
POST /prequalifytakes the borrower's details and checks every bank.POST /prequalify/bankchecks one bank, and accepts parameter overrides such as a different interest rate. That is how a what-if works without touching the registry.
It is a small Node service in Docker, and it is covered by tests.
Checking it against real outcomes
Replacing a system you cannot inspect with one you wrote only helps if the answers are right. The engine ships with a benchmark that replays a set of borrower profiles whose outcome at each bank is already known, and counts true and false positives and negatives. Banks that are not implemented yet are skipped, so the numbers describe what is live.
The result
About 20 seconds became about 100 milliseconds. Both numbers measure the same thing: how long the algorithm takes to return its predictions for a borrower profile, first from the third-party platform and now from our own engine.
The speed is the visible part. The part I care about more is that prequalification is now ours: it answers the same way every time, and when a bank changes its terms, we can reconfigure it ourselves.