We work in your timezone - UK / Europe / Canada / Gulf London Dublin Toronto Dubai
Home / Guides / Fix outsourced code
Guide · Inherited code

How to Fix Bad Outsourced Code

We will say this as an outsourcing firm ourselves, because it would be dishonest coming from anyone else: most bad outsourced code is not bad because it was written far away. It is bad for specific, knowable, structural reasons — and those reasons tell you exactly how to assess what you have inherited, how to fix it, and how to buy the next round of work so it doesn't happen again. This is the practitioner's version, not the marketing one.

In short

Fixing inherited outsourced code is a four-step job. (1) Understand why it went bad — nearly always the commercial model (a fixed-bid race to the bottom with no review, tests, docs or continuity), not the map. (2) Run a triage audit before you commit to any deadline: does it build on a clean machine, are there tests, is the dependency and security health sound, are there secrets in the code, is the framework still supported, is the data model sane, do the licences clear? (3) Stabilise first, then make the fix-vs-rewrite call on evidence — refactor incrementally where the foundations are sound, reset only where they are not. (4) Buy the next round differently: own the repository, bill on milestones, make review and tests deliverables, and keep a senior engineer on your own clock. Honest and accountable outsourcing is a real thing — it just looks nothing like the lowest bid.

Step 1 — diagnosis

Why outsourced code goes bad — and why it isn't about geography

"Offshore equals bad code" is a lazy explanation that lets the real culprit off the hook. Plenty of excellent software is written in Lagos, Nairobi, Kraków and Bangalore; plenty of terrible software is written in London and San Francisco. What actually predicts bad code is the commercial structure the work was bought under. Each of the drivers below is a choice about incentives, and each leaves a recognisable fingerprint in the codebase.

Structural driverWhat it produces in the code
Fixed-bid, lowest-price raceWhen the job is won at the floor price, every hour spent is margin lost. Tests, error handling, input validation and accessibility — everything invisible in a five-minute demo — is quietly cut, because none of it is what got the bid accepted.
No code reviewOne person's blind spots become the permanent architecture. Nothing forced a second pair of eyes over a single line, so the same misunderstanding is repeated consistently across the whole project — which can even make it look deliberate.
No automated testsNobody — including you — can change anything with confidence. Every fix risks silently breaking something else, so the codebase ossifies: cheap to write once, expensive to touch ever again.
No documentationThe knowledge of why anything works lives entirely in one head. The moment that person moves on, the code becomes an archaeology dig for whoever inherits it.
No continuityThe person who built it is not the person who maintains it. A chain of short-lived freelancers, each patching over the last, produces a codebase with no single coherent design and nobody who understands the whole.
No accountabilityAnonymous, one-off work carries no reputational cost for shipping something that merely appears to work. There is nobody to come back to in three months, and everyone involved knew it from the start.

Distance and timezone gaps are real, but they are amplifiers, not causes: they make a weak process harder to catch early. Fix the process — the subject of the last section — and the geography stops mattering. This is not us defending offshore; it is us being precise about what went wrong, so you fix the right thing.

Step 2 — triage audit

Audit what you inherited before you promise anyone a date

The most expensive mistake with inherited code is committing to a roadmap before you know what you are standing on. Run this triage pass first. Each item is a blunt yes/no signal; together they tell you whether you have a codebase to build on or a liability to contain. Do it on a clean machine — a fresh VM or container — because that is where the undocumented steps and hidden assumptions surface.

Procedures last verified July 2026

CheckHow to run itA bad sign
Does it build?Fresh clone on a clean machine, install strictly from the committed lockfile, build and run — using only what is in the repository.It only builds on the original developer's laptop, or needs files nobody can produce.
Are there tests?Find the test suite, run it, read what it covers. The question is not just "does it pass?" but "does it test the paths that make money?"No tests at all, or a token handful that assert 2 + 2 = 4 to make a coverage badge green.
Dependency & security healthRun the ecosystem audit — npm audit, pip-audit, composer audit — and triage findings by exploitability, not raw count.Dozens of high-severity advisories, or dependencies years past their last release.
Secrets in the codeScan the whole git history, not just current files, with gitleaks or trufflehog. Anything found must be rotated at source, not merely deleted.Live API keys, database passwords or private keys committed anywhere in history.
Framework currencyCheck the language runtime, framework and major libraries against their support timelines.A framework or runtime already past end-of-life, receiving no security patches.
Data-model sanityInspect the schema, the migration history and the constraints. Is there a real, versioned data model, or an accretion of columns?No migrations, no foreign keys, duplicated truth across tables, JSON blobs standing in for structure.
Licence checkRun a licence scanner (licence-checker, pip-licenses or an SCA tool) over the full dependency tree, and watch for large verbatim code blocks.Copyleft (GPL/AGPL) dependencies inside proprietary code, or purchased assets under a licence tied to the old developer.

If access itself is still contested — accounts you don't own, a developer who has gone quiet — do the transfer and rotation groundwork first. Our handover checklist for taking over another developer's code covers that stage step by step, including what to do when the handover turns hostile.

Step 3 — fix vs rewrite

Stabilise first, then decide on evidence — not on disgust

Every engineer's first instinct on opening someone else's messy code is "burn it down and start again." Resist it until you have facts. You cannot fairly judge a building while it is on fire, so stabilise before you decide: get it building on a clean machine, rotate the secrets, add tests around the handful of flows that actually earn money, and make one clean deploy under your own credentials. Only from that footing is the fix-vs-rewrite call an engineering decision rather than an emotional one.

And weight the scales honestly. A full rewrite feels clean, but it quietly throws away every edge case the old code learned the hard way — the second-system effect is where rescue projects go to die a second time. Where the foundations are sound, an incremental refactor almost always wins: a strangler-fig migration replaces the code a slice at a time while the product keeps running and earning.

If the codebase turned out to be genuinely abandoned rather than merely rough, our companion guide on how to finish an abandoned app walks through the same decision at project scale.

Which way to lean
  • Refactor & keep when it builds, the data model is sound, the framework is still supported, and the mess is localised — you can improve it a slice at a time
  • Refactor & keep when the business logic is correct even if the code around it is ugly — working logic is expensive to recreate
  • Reset when the framework or runtime is dead and can't be upgraded in place
  • Reset when the data model is fundamentally wrong and everything is built on top of it
  • Reset when there is no safe way to add tests, so no change can be made without fear
  • The deciding question: does the cost of understanding this code exceed the cost of rebuilding it? If yes, reset; if no, refactor
Step 4 — don't repeat it

How to buy the next round so this doesn't happen again

Everything in Step 1 was a consequence of how the work was bought. Change four things and you change the outcome — and none of them require bringing development in-house:

  • Own the repository — and the accounts — from day one. The repo, the cloud console, the domain and DNS should be in your name before a line is written, so the work is always yours to take elsewhere.
  • Bill on milestones, not up front. Pay for delivered, accepted work in stages you can see and test. That single change ties the supplier's payment to quality instead of to signing.
  • Make review, tests and a runbook deliverables — not extras. Write code review, automated tests and a deploy runbook into the scope explicitly. If they aren't named, they are the first things a squeezed budget drops.
  • Keep a senior engineer on your own clock. Real-time overlap means a problem is caught in a conversation the same day, not discovered a week later at the bottom of an email thread.

Here is the honest part, since we are an offshore team ourselves. "Accountable offshore" is not a slogan; it is a specific set of arrangements — the same four above — and you should hold any supplier, us included, to them. What makes it checkable in our case is that we publish our prices. Our Open Price Book is a versioned, fixed set of bands you can read before you ever speak to us, and we bill on milestones — you pay for delivered, accepted work. A price you can read and a payment tied to release are far harder to game than any promise, which is exactly why we lead with them rather than with claims about who we are.

If you are here to…Typical band (ex VAT)
Stabilise an inherited codebase (rescue takeover)£8,000–£12,000 to take it over; completion priced from the project bands
Rebuild a validation-stage web MVP£12,000–£20,000
Build a standard SaaS MVP£16,000–£30,000

Prices published from our Open Price Book (v1.0 · July 2026 · next review October 2026). All prices exclude VAT.

Questions & answers

Fixing outsourced code — FAQ

Why does outsourced code so often turn out badly?
Rarely because it was written far away — almost always because of the commercial model it was bought under. A fixed-bid job won at the lowest price rewards the seller for cutting every corner you cannot see: no automated tests, no error handling, no code review and no documentation, because each of those is unpaid hours against a fixed number. Add a revolving door of freelancers with no continuity and no reputational stake, and you get code that demos well and collapses under its second feature. Distance and timezone gaps make the problem harder to catch early, but the root cause is the incentive structure, not geography. Change the model — milestone billing, code review, tests as deliverables, one accountable team — and outsourced code behaves like any other well-run codebase.
How do I check whether inherited code is safe to build on?
Run a triage pass before you promise anyone a deadline. In order: does it build from a clean clone on a fresh machine using only the committed files; is there a test suite, does it pass, and does it cover the paths that make you money; run the ecosystem audit command (npm audit, pip-audit, composer audit) for known vulnerabilities; scan the git history for hardcoded secrets with a tool like gitleaks or trufflehog; check whether the framework and runtime are still supported or past end-of-life; sanity-check the data model for a real schema, migrations and referential integrity; and run a licence scanner over the full dependency tree for copyleft surprises. Each check is a yes/no signal, and together they tell you whether you are inheriting a codebase or a liability.
Should I fix the inherited code or rewrite it from scratch?
Stabilise first, then decide — you cannot judge a building while it is on fire. Get it building, rotate the secrets, add tests around the parts that earn money and make one clean deploy under your own credentials. Only then choose. Lean towards keeping and refactoring incrementally when it builds, the data model is sound, the framework is still supported and the mess is localised; you can improve those codebases a slice at a time while they keep running. Lean towards a reset when the framework is dead, the data model is fundamentally wrong, there is no safe way to test, or security problems run throughout — when the cost of understanding it exceeds the cost of rebuilding it. Full rewrites are riskier than they feel: you discard years of battle-tested edge cases, so prefer an incremental strangler-fig migration wherever it is viable.
What are the first security things to check in outsourced code?
Secrets and access first. Scan the entire git history — not just the current files — for committed API keys, database passwords and tokens, because deleting a secret from today's code does not remove it from history; anything found must be rotated at the source, not just deleted. Then rotate every credential the previous team ever held, on the assumption copies exist on hardware you do not control. Run the dependency audit for known vulnerabilities and triage by exploitability rather than raw count. Finally confirm you own every account the app depends on — repository, cloud console, database and third-party services — so nobody else can lock you out. Our companion guide on taking over another developer's code covers the access-transfer and rotation steps in full.
How do I avoid ending up with bad outsourced code again?
Change the four things that let it happen. Own the repository, cloud and domain accounts from day one so the work is always yours. Pay on milestone billing — for delivered, accepted work — rather than up front, so quality is tied to release. Make code review, automated tests and a deploy runbook contractual deliverables, not optional extras you discover were skipped. And keep at least one senior engineer on your own working hours so problems are caught in a conversation, not a week later by email. None of that requires you to bring development in-house; it requires an outsourcing arrangement built for accountability rather than the lowest bid.
What does accountable outsourcing actually cost?
More than the cheapest freelancer and typically 40 to 70 per cent below a local agency for comparable work. We publish every band in our Open Price Book rather than quoting from behind a curtain: a validation-stage web MVP sits in the £12,000–£20,000 range and a standard SaaS MVP in the £16,000–£30,000 range, all ex VAT. Rescuing an inherited codebase usually starts as an app-rescue takeover in the £8,000–£12,000 range to stabilise it, with completion then priced from the same published project bands. The point of publishing the numbers is that a fixed, versioned price you can read before you talk to us is harder to game than any promise — it is the part of our model we would ask you to judge us on.

Inherited a codebase you're not sure is worth saving?

Book a free 30-minute call with a senior engineer — in your business hours. We'll run the triage checklist against your actual project, give you a straight fix-or-reset read, and put a published price band against whatever comes next. No obligation, and no answer dressed up to win the work.

Book a free scoping call →