Common mistakes every junior developer makes (and how to avoid them)
Every senior engineer has reviewed hundreds of junior pull requests, and they all flag the same 15 mistakes. None of them are about not knowing algorithms, they're about engineering judgment. Here they are, in advance, so you can skip the embarrassment and ship cleaner code in your first month.
1. Over-engineering simple problems
The classic junior move: turning a 10-line script into a class hierarchy with three abstractions, a strategy pattern, and a config file. The intent is good, "I'm thinking about extensibility", but you're adding cost (more code, more places to break, harder to read) for benefits that may never come.
Senior rule: write the minimum that solves today's problem. Add abstraction only when you have at least three concrete cases that need it. Three uses, then abstract is a useful default.
What it looks like in code review
// Junior:
class UserRepositoryFactory {
static create(config: RepoConfig): IUserRepository {
return new UserRepository(config);
}
}
// Senior asks: do we ever need more than one implementation?
// Junior says: not yet
// Senior: "Then just export a function. We can refactor when we need to."
2. Bad variable and function names
Code is read 10x more than it's written. A variable named data or temp or handleData tells the next reader (often you, three months later) absolutely nothing.
Senior rule: a variable's name should describe its meaning, not its type. users is better than arr. activeUsers is better than users. activeUsersInTrial is even better when accurate.
For functions, the name should describe what it returns or what it does, in the active voice: getUsersInTrial(), not doUserStuff().
3. Comments that say what the code says
Junior code review favorite:
// Increment i by 1
i++;
// Get the user
const user = await getUser(id);
These comments are noise. They duplicate what the code already shows and rot when the code changes.
Senior rule: comments should explain why, not what. The "why" is invisible from the code, it's the constraint you can't see.
// Stripe webhook events can arrive out of order; we dedupe on event.id
// to avoid double-charging users when a retry overlaps the original.
if (await wasProcessed(event.id)) return;
That comment is gold. It tells future-you something the code can't.
4. Silent failures (the worst bug class)
Junior code:
try {
await sendWelcomeEmail(user);
} catch (err) {
// ignore, not critical
}
Three months later, your email provider has been broken for two weeks and nobody noticed. Users aren't getting welcome emails. Support tickets pile up. You debug for a day before finding the empty catch block.
Senior rule: never swallow exceptions silently. At minimum, log them. Better: log them with structured context (user_id, error message, stack) so they show up in your monitoring dashboard. Best: alert on patterns of these errors so you find out within an hour.
5. Hardcoded secrets in commits
Every junior commits an API key to GitHub at least once. Sometimes it's SECRET_KEY = "dev_secret_123" as a quick test, then a hurried git push.
That's now in your Git history forever, even if you delete it in the next commit. Public repos get scraped within minutes by bots looking for AWS keys, GitHub tokens, etc.
Senior rule:
- All secrets in
.envfiles..envin.gitignore. - Use
.env.examplewith placeholders so others know what to set. - Set up
git-secretsor pre-commit hooks that block known patterns. - If you ever do leak a secret, rotate it immediately. Don't just delete the commit, the secret is already public.
6. Premature optimization
Junior writes:
// Use a Map for O(1) lookup performance
const users = new Map();
for (const u of userList) users.set(u.id, u);
return users.get(targetId);
For 5 users. The lookup happens once. The original loop would have run in microseconds.
Senior rule: write the obvious version first. Profile if it's slow. Optimize only with measurements. "I think this might be slow" is not a measurement.
The exception is for code that runs at scale (every API request, in a loop over millions of items). For one-off scripts and small data, the simple version always wins.
7. Mass-replacing without reading what you're replacing
You see a function in the codebase named getUserData that you want to rename to fetchUser. Find-and-replace, save, push.
What you missed: a string literal in a config file {"action": "getUserData"} got mangled. A test name describe('getUserData') now fails. A comment that referenced the old name is now wrong.
Senior rule: use your editor's "Rename Symbol" (F2 in VS Code) instead of find-and-replace for code. It's syntax-aware and only renames actual references. Always run tests after a rename.
8. Bad commit messages
Junior commit log:
fix
update
asdf
fix again
WIP
final
final final
Six months later, when something breaks and someone runs git blame, these commits tell them nothing. Useful debugging context, why this line is necessary, is gone forever.
Senior rule: follow Conventional Commits. fix:, feat:, refactor:, chore:, then a one-line description of what changed and why. Rewrite the message before pushing if it's bad.
Practice writing commits seniors would approve of
InternQuest grades your commit messages on every mission, conventional format, length, imperative mood, no placeholders. The same checks senior reviewers run, automated for practice. Free.
Train your commit muscle →9. Not reading the existing patterns before writing new code
Every codebase has its own conventions, error handling, logging, where helpers go, how config is loaded. Juniors often write their feature in their personal style, ignoring the existing patterns. The PR comes back with 30 comments saying "we do this differently here."
Senior rule: before adding a new feature, find the closest existing feature and copy its structure. Same file layout. Same error handling. Same logging. Same naming conventions. Consistency is more valuable than your personal preference.
10. Catching errors too broadly
# Junior:
try:
response = api.get(url)
user = parse_user(response)
save_to_db(user)
except Exception as e:
print(f"Error: {e}")
If the network fails, you log "Error: connection refused." If the parse fails, you log "Error: KeyError." If the DB save fails, you log "Error: integrity violation." All look the same in your logs. You can't tell which step failed.
Senior rule: catch specific exceptions where you can handle them, near the operation that might raise them. Let unexpected ones propagate up to a top-level handler that logs richly.
try:
response = api.get(url)
except (NetworkError, Timeout) as e:
log.warning("api_fetch_failed", url=url, error=str(e))
return None
# Let parse and save errors propagate, they're real bugs, not transient failures.
11. Modifying state during iteration
# Buggy
for user in users:
if user.expired:
users.remove(user) # mutating the list you're looping over
# Right
users = [u for u in users if not u.expired]
This bug is silent, Python won't crash, but it'll skip elements unpredictably. Then you have a "ghost" bug where some expired users don't get removed, and you can never reproduce it consistently.
Senior rule: never mutate a collection while iterating over it. Build a new collection, or collect indices to remove and apply them after the loop.
12. Floating-point equality
// JS
if (0.1 + 0.2 === 0.3) { ... } // false! 0.1+0.2 = 0.30000000000000004
Floats are not exact. Comparing them with == or === almost always leads to bugs.
Senior rule: compare floats with a tolerance: Math.abs(a - b) < 1e-9. For money, never use floats, use integers (cents) or a Decimal type.
13. Off-by-one errors in slicing/ranges
// Pagination, juniorversion:
const start = page * pageSize;
const end = start + pageSize - 1;
items.slice(start, end); // misses the last item
// Right (slice end is exclusive):
items.slice(start, start + pageSize);
Off-by-one is the second-most-common bug class after null reference errors. Always test pagination with at least 1, 2, exactly-pageSize, and pageSize+1 items.
Senior rule: when in doubt, write the test before the implementation. page 1 of 25 items with pageSize 10 should return items 0-9. page 3 should return items 20-24. Be explicit.
14. Not knowing your language's gotchas
Every language has 5-10 footguns that bite every newcomer. Learn yours:
JavaScript
== vs ===(always use===)thisbinding in callbacks (use arrow functions or.bind)varhoisting and scoping (useconst/letonly)- Implicit type coercion:
[] + [] === ""
Python
- Mutable default arguments (
def foo(x=[])shares the same list across calls) isvs==(use==for value equality,isonly forNone)- List comprehensions vs generators (memory difference)
- Late-binding closures (
lambdasin a loop)
Go
- Capturing loop variables in goroutines
- Nil interface vs nil concrete type
- Slices sharing underlying array memory
Spend an hour learning your language's footguns before your first big PR. It saves days of "why isn't this working?"
15. Treating code review as personal criticism
Your first 50 PRs will get a lot of comments. Some will sting. The instinct is to feel attacked, get defensive, or worse, argue with the reviewer.
Senior rule: code review is feedback on the code, not on you. The goal is to ship better code, not to be right. Even if you think a comment is wrong, your default response should be:
- Read the comment carefully.
- If you understand and agree, fix it.
- If you don't understand, ask for clarification.
- If you disagree, explain your reasoning calmly with one sentence, and yield gracefully if the reviewer pushes back.
Engineers who get defensive about code review are the ones who get stuck as juniors. Engineers who absorb feedback and improve get promoted faster than seems possible.
The meta-mistake: not asking when stuck
Beneath all 15 of these is one thing, most junior mistakes are caught and fixed by asking before shipping. The fear of "looking dumb" causes more bugs than any technical gap.
The 30-minute rule: if you've been stuck on something specific for 30 minutes, ask. Not "I'm stuck", a specific question with the context you've already gathered. ("I'm trying to add a new field to the User model. I added it to models/user.py and ran the migration, but the API still returns 404. I checked the route is registered. What am I missing?")
Senior engineers love getting questions like that. It's the vague "I'm confused, can we pair?" that frustrates them.
What to do with this list
Don't try to memorize all 15. Bookmark this page. The next time you write a PR, scan it once before requesting review and ask: did I commit any of these? You'll catch about 70% of the issues your reviewer would have flagged.
That alone, submitting cleaner PRs from day one, will get you noticed. Senior engineers form opinions of juniors fast, and "their PRs are usually solid" is one of the strongest.
Practice catching these mistakes before your first real PR
Every InternQuest mission grades you on conventional commits, code quality (long functions, single-letter names, missing docstrings), and the bug you were sent to fix. The same checks a senior reviewer runs, automated, instant, free.
Try a mission →