Evaluating a codebase before you sign the offer isn’t about grading the code. It’s about diagnosing the team. A repo is a fossil record of every management misfire, every stupid deadline, and every architecture argument someone lost in a conference room. The code itself is rarely the villain. The patterns of neglect, over-engineering, and sheer terror frozen into the directory structure—those are what will grind you down. I call this organizational archaeology. Treat the codebase as an artifact of the engineering culture, and you’ll spot the red flags no hiring manager will ever mention. If you’re a senior engineer, a staff engineer, or a CTO walking into a turnaround, this is your due diligence. It’s the difference between joining a team that builds and a team that just patches.
The Commit Log as a Cultural Autopsy
Forget the README. The commit history is the most honest document in the repository. It tells you who works weekends, who owns what, and whether the team treats version control as a communication tool or a backup script. Start by running git log --format='%an' | sort | uniq -c | sort -nr. If 80% of the commits come from one person, you’re not joining a team. You’re joining a single point of failure with a supporting cast. That person is probably burned out, overly critical in code reviews, and has veto power over every technical decision because they hold all the context in their head.
Next, look at the commit messages. Are they descriptive, or do you see a wall of “fix,” “update,” and “wip”? A team that writes lazy commit messages writes lazy code. They’re not thinking about the maintainer, which means they’re not thinking about you. One particularly damning pattern is the “megacommit”—a single commit that touches 40 files across the UI, API, and database layers. This is a sign of a team that doesn’t understand separation of concerns or is forced to ship features in a panic. Either way, it means the codebase is a monolith in practice, even if it’s split into microservices on paper.
The Blame Game
Use git blame on the files that change most frequently. If you see the same few names on every hot file, you’ve found the team’s bottlenecks. More importantly, look for files where the original author is long gone and the current maintainers are just adding patches on top of patches. These are the “haunted forests” of the codebase—areas everyone is afraid to refactor because nobody understands the original intent. A healthy team has a rotation of names on every file. A sick team has a graveyard.
The Dependency Manifesto
Open the package manager file—package.json, requirements.txt, go.mod, whatever. This is a political document. It tells you how the team balances stability against novelty, and how much they trust external maintainers. First, check the version pinning. If every dependency is pinned to an exact patch version, the team has been burned by a breaking change in the past and now trusts no one. That’s not necessarily bad—it’s a scar that tells a story. But if dependencies are unpinned and the project is more than a year old, you’re looking at a team that doesn’t know what reproducibility means.
Count the number of dependencies. A typical web application should not need 1,500 npm packages to render a form. A bloated dependency tree is a sign of architectural laziness—pulling in a library for a three-line function because nobody wanted to write a utility module. It’s also a security nightmare. Check for abandoned packages. Run npm outdated or the equivalent. If you see packages that are two major versions behind, ask about it in the interview. If they say “we’ve been meaning to upgrade but it’s not a priority,” what they mean is “we do not invest in maintenance, and you will be the one to fix it when it breaks.”
Forking and the “Not Invented Here” Syndrome
Look for internal forks of public packages. A team that maintains its own fork of a common library has either a very good reason or a very bad case of control-freak syndrome. If the fork exists because the team needed a specific patch and contributed it upstream, that’s a sign of maturity. If the fork exists because “we didn’t like the direction of the project” and it’s now 200 commits behind upstream, you’re looking at a team that builds silos. They’ll build one around you too.
Testing: The Team’s True Religion
Don’t ask “do you write tests?” Every team says yes. Instead, look at the test files. Run the test suite and watch the output. If the tests take 45 minutes to run and half of them are skipped, the team has given up on testing as a practice. They keep the suite around like a gym membership—it makes them feel better, but they never use it. Check the coverage reports, but don’t obsess over the percentage. 90% coverage can be a lie if the tests are just calling functions without assertions. Look at the quality of the tests. Are there tests that mock the entire universe and test nothing but the mocking framework? That’s a team that values ceremony over safety.
One of my favorite tricks is to look for the oldest test in the suite. If it’s a brittle, end-to-end test that breaks on every third commit and has been commented out and uncommented six times, you’ve found the team’s collective trauma. That test represents a production outage from three years ago that nobody has had the courage to properly fix. The team’s entire deployment process is probably built around placating that one test.
The Flaky Test Graveyard
Search the codebase for keywords like “skip,” “ignore,” “flaky,” “TODO,” and “FIXME.” A healthy codebase has a few of these, clustered around areas of active development. A sick codebase has hundreds, scattered like confetti, some dating back to the initial commit. Each one is a broken promise. The team has learned to live with them, which means they’ve learned to live with broken windows. That attitude will infect every other engineering practice.
Architecture as Organizational Chart
Conway’s Law isn’t a theory; it’s a physical law of software. The codebase structure will mirror the communication structure of the team that built it. If the codebase is split into perfectly decoupled microservices but the team is 12 people sitting in one room, someone has been reading too many blog posts. That architecture will crumble the first time a cross-cutting change is needed, because the organizational boundaries don’t match the service boundaries. Conversely, if the codebase is a monolith but the team is split into three squads on different continents, you’ll spend half your time in merge conflict resolution.
Look at the directory structure. Does it reflect the domain, or does it reflect the framework? A codebase organized by technical role—controllers/, models/, views/—is a sign of a team that thinks in terms of technology, not in terms of the problem they’re solving. A codebase organized by feature or domain—invoicing/, user-management/, search/—is a sign of a team that understands the business. The former is easy to navigate if you know the framework; the latter is easy to navigate if you know the product. Which one do you want to learn?
The Shared Kernel Anti-Pattern
Watch out for a common/ or shared/ or utils/ directory that everything depends on and nobody owns. This is the tragedy of the commons in code form. It becomes a dumping ground for functions that two teams needed but neither wanted to maintain. Over time, it accretes circular dependencies and becomes impossible to refactor. If you see a utils.js with 2,000 lines and no tests, run.
Documentation: The Lies We Tell Ourselves
Every team has a README.md. Most of them are fiction. The real documentation is in the Slack channel, the wiki that nobody updates, and the tribal knowledge stored in the tech lead’s head. But you can still learn from the artifacts. Check the last commit date on the documentation files. If the CONTRIBUTING.md was last touched two years ago and still references a branching strategy the team abandoned, you’re looking at a team that doesn’t onboard well. You’ll be expected to “figure it out.”
Look for architecture decision records (ADRs). These are short documents explaining why a particular technical decision was made. If the team has a directory full of ADRs, they’re thoughtful and intentional. If they have none, decisions are made in hallways and forgotten. Ask in the interview: “Can you show me an example of a recent ADR?” If they blink at you, that’s your answer.
Deployment and Operations Scars
The CI/CD pipeline is the team’s nervous system. If it’s slow, flaky, and requires manual intervention, the team is operating in a constant state of low-grade stress. Look at the build history. A pipeline that fails on every other commit and requires a “re-run” is not a pipeline; it’s a suggestion. Check the deployment frequency. If the team deploys once a month and it’s a big ceremony with a rollback plan and a war room, they don’t trust their own code. That means they don’t have good testing, good monitoring, or good rollback mechanisms. You’ll spend your first six months just making deployments boring again.
Look at the infrastructure-as-code files. If the production configuration is in a separate, private repository that only the ops team can see, you’re joining a team with a wall between developers and operations. That wall is where responsibility goes to die. You’ll write code, throw it over the wall, and hope it works in production. When it doesn’t, you won’t have the access to debug it.
Monitoring and Alerting
Ask to see the production dashboards. If the team doesn’t have dashboards, or if the dashboards are all green because the alerting thresholds are set to “never page,” you’re joining a team that flies blind. Look for the on-call rotation. If there’s no rotation, or if the same person has been on call for six months, the team doesn’t take operational responsibility seriously. That person is a martyr, and you’ll be expected to join the religion.
How to Ask About What You Find
You now have a list of red flags. Don’t walk into the interview and recite them like a prosecutor. That’ll just make people defensive. Instead, frame your questions as curiosity. “I noticed the test suite takes about 30 minutes to run—how does that affect your development workflow?” If they say “oh, we just run the tests locally and only run the full suite in CI,” they’ve acknowledged the problem and have a workaround. If they say “it’s fine, we’re used to it,” they’ve normalized the pain. That’s a cultural problem, not a technical one.
Ask about the last time they deleted a significant amount of code. A team that never deletes code is a team that’s afraid of their own codebase. They’re hoarders. Ask about the worst bug they shipped in the last year. If they can’t think of one, they’re either not monitoring production or they’re lying. If they tell you a story with a beginning, middle, and end—including the postmortem and the changes they made—you’re talking to a mature team that learns from failure.
FAQ
What if I don’t have access to the codebase before I join?
Many companies won’t give you repository access until your first day. That’s normal. But you can still ask for a guided tour during the interview process. Ask the hiring manager: “Can one of the engineers walk me through the codebase structure and the CI/CD pipeline?” If they refuse, that’s a red flag in itself. If they agree, you can ask the same diagnostic questions while screen-sharing. Pay attention to how the engineer navigates the code. Do they know where things are, or are they searching and guessing? That tells you how well the team shares context.
What’s the single biggest red flag in a codebase?
A lack of tests combined with a lack of documentation. Either one alone is survivable. You can add tests to undocumented code, or you can document untested code. But if neither exists, the team is operating on oral tradition alone. That means the codebase is a black box, and the only way to understand it is to ask the elders. If the elders leave—and they will—you’re left with a haunted forest and no map. This is the most common failure pattern I see in startups that grew too fast.
How do I evaluate a codebase that’s being rewritten?
Rewrites are the nuclear option of software engineering, and they usually fail. If the team is actively rewriting the codebase, ask why. If the answer is “the old code is a mess and we want to start fresh,” you’re joining a team that doesn’t understand the business logic embedded in that mess. The rewrite will take three times longer than estimated and will miss critical edge cases that the old system handled. A better answer is: “We’re incrementally replacing specific modules because the old architecture can’t scale for reason X.” That shows they understand the tradeoffs and aren’t just chasing technical vanity.
What tools can I use to automate this evaluation?
Static analysis tools can give you a quick health check. cloc counts lines of code and breaks them down by language, which helps you understand the codebase composition. dependency-cruiser visualizes dependency graphs and can spot circular dependencies. code-maat is a command-line tool that mines commit history for patterns like change frequency, author churn, and hotspots. These tools don’t replace human judgment, but they can confirm or refute your gut feelings with data. If you want to go deeper, Adam Tornhill’s work on code as a crime scene is the definitive resource on mining software repositories for organizational insights.
The Decision Framework
After you’ve done your archaeology, you need to make a decision. I use a simple framework: separate the problems into “technical debt” and “cultural debt.” Technical debt is fixable. You can refactor a monolith, add tests, upgrade dependencies. Cultural debt is not fixable by you. If the team doesn’t value testing, doesn’t write documentation, doesn’t do postmortems, and doesn’t delete dead code, you won’t change that as a new hire. You’ll either burn out fighting it or assimilate and become part of the problem.
Your goal isn’t to find a perfect codebase. Perfect codebases don’t exist. Your goal is to find a team whose problems you’re willing to live with. Every codebase has scars. The question is whether the team is healing those scars or just hiding them under bandages. Join a team that debrides its wounds. Avoid a team that pretends they’re not bleeding.
This is the first article in what I plan to make a recurring column: Organizational Archaeology. Next up: how to run a codebase audit in your first 30 days without making everyone hate you. If you have a horror story from a codebase you inherited, I want to hear it. The worst ones are always the most educational.


