
How to Spot Bad Code (Even If You Don’t Write Any)
You don’t need to know a programming language to tell when code is in rough shape. A lot of “bad code” looks bad in ways anyone can see — the same way you can tell a kitchen is messy without being a chef.
You might encounter this more often than you expect. A vendor submits a codebase for your review. An engineer hands off a project to your team. You’re evaluating whether to build or buy something, and the team that built it is trying to earn your trust. In each of those situations, you don’t need to write code to ask useful questions about it.
Here’s a checklist of smells to scan for. Skim through code you’ve been asked to review, audit, or just understand, and watch for these patterns.
Functions That Are Way Too Long
A “function” is a labeled block of code that does one job. If you scroll and scroll and you’re still inside the same function — hundreds of lines, no end in sight — that’s a smell. Good code breaks work into small pieces with clear names. Long functions are a sign the engineer wasn’t thinking about who would read this later.
Rule of thumb: if a function doesn’t fit on your screen, it’s probably doing too much.
Names That Don’t Tell You Anything
Look at the variables and functions. Do they have meaningful names, or are they called x, data, temp, foo, thing, doStuff()?
Bad
let d = new Date();
let x = u.filter(i => i.a > 18);
Better
let today = new Date();
let adults = users.filter(user => user.age > 18);
You don’t need to understand the syntax to feel the difference. One version tells you what’s happening; the other makes you guess.
Copy-Pasted Blocks
If you see the same chunk of code appearing four times in a row with tiny changes, that’s a maintenance landmine. When the rule changes, someone has to remember to update all four copies. And they won’t.
There’s a principle in software called “Don’t Repeat Yourself.” The idea is that each piece of knowledge should live in exactly one place. When it doesn’t, the codebase becomes unreliable over time — bugs get fixed in one copy but not the others.
Skim for visually identical blocks stacked near each other. Your eye will catch it.
Magic Numbers
Random numbers sprinkled in with no explanation. Why 86400? Why 0.0825? Why 42?
Bad
if (user.age >= 21 && total > 86400) { ... }
Better
const LEGAL_DRINKING_AGE = 21;
const SECONDS_PER_DAY = 86400;
if (user.age >= LEGAL_DRINKING_AGE && total > SECONDS_PER_DAY) { ... }
Named constants tell the next reader why the number matters. Numbers without context are a form of hidden knowledge that lives only in the original author’s head.
Commented-Out Code Left Lying Around
Big blocks of code wrapped in comments that nobody removed. It usually means someone wasn’t sure if they’d need it back. And the longer it sits, the more nobody knows what it was for.
This is often a sign the team doesn’t fully trust their version control system. A healthy codebase deletes dead code and trusts version history to keep it findable. If the code is commented out “just in case,” that’s a discipline problem.
TODOs and FIXMEs Piling Up
Search the code for the words TODO, FIXME, HACK, or XXX. A few are fine. Hundreds are a sign of debt nobody’s been allowed to pay down.
This is one of the easiest checks to run during a vendor evaluation. Ask the team to search for TODO markers while you watch. The count is a rough proxy for how much the team has been cutting corners under pressure.
The Staircase of Doom
Deeply nested `if` statements, each one indented further than the last. The code drifts toward the right side of the screen like a staircase.
if (user) {
if (user.account) {
if (user.account.active) {
if (user.account.balance > 0) {
if (user.account.verified) {
// finally do the thing
}
}
}
}
}
You can literally see the problem — the shape of the code is wrong. There are cleaner ways to write that, and engineers who know them don’t write code that looks like this.
Comments That Explain “What” Instead of “Why”
Good comments tell you why something is happening. Bad comments restate the code in English.
Bad
// add 1 to counter
counter = counter + 1;
Better
// Skip the header row before counting data rows.
counter = counter + 1;
If a file has lots of the first kind, it’s a sign nobody trusted the names to speak for themselves. It also suggests the code was written quickly, without much thought for the person who would read it later.
Functions With a Parade of Arguments
When a function takes nine things as input, the caller has to get all nine right and in the correct order. That’s hard to use and easy to break.
createUser(name, email, age, address, city, state, zip, country, role, active, ...);
If you see signatures like that, expect bugs. The better pattern is to group related inputs into a single object with named fields — that way, order doesn’t matter and the intent is clear. When you see functions like the one above, it usually means the code grew by accretion rather than design.
The God File
One file that’s thousands of lines long and seems to do everything. If you open a file called something innocuous like utils.js or helpers.py and it’s 4,000 lines, that’s a dumping ground. Important behavior is hiding in there and nobody can find it.
Healthy codebases organize code into files with clear, narrow responsibilities. When code gets dumped into a catch-all file, it signals that the team never took time to think about structure. Over time, everyone starts adding to it because that’s where things already are. It’s a debt spiral.
Errors That Get Swallowed
When something goes wrong, code is supposed to either handle it or pass it along. Watch for blocks that catch errors and do nothing:
try {
doSomethingRisky();
} catch (e) {
// ignore
}
That comment is a confession. The error happened, and nobody will ever know. From a business perspective, swallowed errors mean silent failures: your system thinks everything is fine, but data isn’t being saved, payments aren’t going through, or messages aren’t being sent. These are the hardest production bugs to diagnose.
Inconsistent Style
Different files (or worse, different parts of the same file) using different formatting, naming, or structure. username here, user_name there, UserName in a third place.
It usually means the project doesn’t have automated formatting set up, which means a lot of other discipline is probably missing too. Consistent style is a signal that the team has agreed on standards and enforced them. Inconsistent style means they haven’t.
No Tests, or Tests That Don’t Run
Look for a folder named tests/, spec/, or files ending in .test, .js, _test.py, etc. If there’s nothing — or if the test folder exists but everything is skipped or commented out — the team has no automated way to know they broke something.
For a non-technical stakeholder, the practical consequence is this: every time the team ships a change, they’re guessing. They might be right most of the time, but they have no systematic way to catch regressions. As the system grows, that guesswork gets harder and bugs get more expensive to find.
Quick tip: ask the codebase to run its own tests. If nobody on the team can remember the command, that’s its own answer.
The 30-Second Scan
If you only have a minute, look for:
- Functions or files that scroll forever
- Variable names like
x,tmp,data - Blocks of repeated, copy-pasted code
- Unexplained numbers in the middle of logic
- Commented-out code that’s been there a while
- Lots of
TODO/FIXMEmarkers - Deeply indented “staircase” structures
- Error handlers that do nothing
- No tests, or tests that are all skipped
One Last Thing
You don’t need to be able to fix any of these to be useful. Pointing them out, in a review, in a vendor evaluation, in a conversation with the engineers who own the code,is already valuable.
The people writing the code often stop seeing the smells because they’ve lived with them too long. A fresh pair of eyes that knows what to look for is a real contribution. And when you can name the problem specifically, you’re having a more productive conversation than “this feels off.”
You’re not there to judge the engineers. You’re there to ask whether the codebase can support the business over time. These thirteen things will give you a clear signal.


