Test-Driven Development (TDD) is one of the most debated methodologies in software development. Some swear by it, claiming it leads to better code quality, fewer bugs, and more maintainable systems. Others—myself included—find that it disrupts productivity, stifles creativity, and often becomes an unnecessary burden.
If you’ve ever struggled with TDD or felt that it slows you down, you’re not alone. Here’s why I don’t use TDD and why you might want to reconsider its place in your workflow.
1. TDD Slows Down Development
TDD enforces writing tests before writing the actual implementation. While this sounds great in theory, in practice, it drastically slows down development, especially in the early stages of a project when:
- Requirements are still evolving.
- You’re rapidly iterating on ideas.
- The code you write might change or be thrown away.
In fast-moving environments, writing tests before you even know what the final implementation should look like is often a waste of time.
2. It Disrupts Developer Flow & Creativity
Developers often get into a state of flow where they can think deeply and solve complex problems efficiently. TDD forces constant context-switching:
- Write a test.
- Write just enough code to pass the test.
- Refactor.
- Repeat.
This cycle is jarring and prevents you from thinking holistically about the problem. Many experienced developers prefer to focus on designing elegant, well-structured code first, then write tests after they have a solid foundation.
3. Not All Code Benefits from TDD
TDD is great for well-defined logic-heavy components (like a sorting algorithm), but in real-world development, a lot of code is exploratory. Some examples where TDD is more of a hassle than a benefit:
- UI Development: Frontend behavior is highly visual and interactive. Writing tests before UI elements even exist is impractical.
- Machine Learning / AI: Expected outputs aren’t always deterministic, making strict test cases difficult to define.
- Game Development: Physics, AI behavior, and procedural generation don’t fit neatly into test-driven workflows.
In these cases, manual testing, logging, and debugging are often much more effective than writing unit tests upfront.
4. TDD Can Create a False Sense of Security
One of the biggest arguments for TDD is that it prevents bugs. However, just because code passes tests doesn’t mean it’s bug-free or well-architected.
Why?
- Tests only check for expected behavior. They don’t catch unknown issues.
- Poorly written tests can lead to false confidence in bad code.
- Many bugs occur due to integration issues, which TDD does not directly address.
Instead of blindly following TDD, developers should focus on proper code reviews, integration tests, and real-world testing.
5. TDD Doesn't Guarantee Good Code
TDD advocates claim it leads to better architecture, but that’s only true if the developer already knows how to design good software.
- Good design comes from experience and critical thinking, not rigid test-first rules.
- Writing tests too early can lock you into suboptimal architectures, making future refactoring harder.
Great developers don’t just follow processes, they think critically about when a methodology is useful and when it’s just extra work.
6. Most Companies Don’t Rigidly Follow TDD Anyway
Despite its popularity in theory, very few teams strictly adhere to TDD in practice. Many companies instead:
- Write tests after implementation to ensure correctness.
- Focus more on integration and end-to-end testing over rigid unit tests.
- Prioritize fast iteration and delivery over TDD’s structured approach.
Even Kent Beck, the creator of TDD, has admitted that he doesn’t follow it religiously anymore.
So, Should You Ditch TDD Completely?
Not necessarily. TDD has its place, but it’s not the universal solution that some make it out to be. It’s just one tool among many.
👉 When to use TDD:
✅ When working on critical logic-heavy components (e.g., compilers, financial calculations, authentication systems).
✅ When building a system that requires high reliability.
✅ When working in a team that values test-first development.
👉 When to avoid TDD:
❌ When working on rapidly evolving codebases.
❌ When developing UI-heavy applications.
❌ When exploring new ideas and architectures.
Conclusion
TDD isn’t a silver bullet. While it might work well in some situations, blindly applying it to every project can slow you down, disrupt your workflow, and lead to unnecessary complexity. Instead of forcing yourself into a TDD-first mindset, focus on writing good code first, then add tests where they provide the most value.
At the end of the day, what matters is delivering high-quality software efficiently—not blindly following a methodology for the sake of it.
This youtube video from ThePrimeTime gave me good insight, you should check it out.
https://youtu.be/0IhsNvUZS3s
Let me know in the comments what you think and how you've implemented TDD in your projects
Top comments (7)
I agree with the conclusion, don't follow a concept by the letter. If a concept leads to dumb or crazy situations you should avoid that.
There are some things that worry me in the post.
How do you know it is good code? Because the code works when you use it? Good is not a metric to base your code on.
What is the value scale of tests? If the code you add is not covered by tests, a test should be added.
So those applications don't need to be reliable? How are you going to know if changes don't break things in unexpected places?
How do tests lock you in? A change in code also changes tests.
Good design, experience and critical thinking have nothing to do with the process of writing the code.
Is TDD for you only writing unit tests? TDD doesn't single out any form of testing, AFAIK.
If the piece of code you are going to write can have multiple outcomes, you have to write tests for all the outcomes. Here is where I differ from the strict interpretation of TDD.
That is not an excuse to skip tests
That is why the tests exist. Change the code how much you like the outcome will be the same.
Before you start coding requirements are established. With that knowledge alone you can write tests. If you have a deeper knowledge of the application you also know the layers that will require tests.
While I don't follow the strictest form of TDD. For the most part I write tests and code together. Tests should not be an afterthought.
Thanks for the thoughtful response! I think we actually agree on many fundamental points—tests are important, but blindly following any methodology without considering the context can lead to unnecessary complexity. I’d like to clarify my stance on a few of your points:
1. "Focus on writing good code first"
→ How do you know it is good code?
"Good code" isn’t just about whether it works when executed. It refers to maintainability, readability, scalability, and adherence to solid design principles. Tests help verify correctness, but they don’t inherently make code good. Prioritizing clear structure and logical separation before rigidly writing tests-first can lead to more maintainable code in the long run.
2. "Add tests where they provide the most value"
→ What is the value scale of tests? If the code you add is not covered by tests, a test should be added.
Not all code carries the same risk of failure. Tests provide the most value when they cover complex business logic, edge cases, and critical system behavior. For example, testing a pure function that calculates a price discount is more valuable than testing a simple UI component that displays static text. The idea isn’t to avoid testing but to be pragmatic about what to test first.
3. "When not to use TDD – UI-heavy applications & rapidly evolving codebases"
→ So those applications don’t need to be reliable? How are you going to know if changes don’t break things in unexpected places?
They absolutely need to be reliable! However, UI-heavy applications tend to change frequently, and writing tests before the UI design stabilizes can lead to constant rewrites. UI tests often benefit more from snapshot, integration, and E2E testing rather than strict TDD. Similarly, in a rapidly evolving codebase, writing tests before finalizing architecture can lead to wasted effort as features shift. This doesn’t mean skipping tests entirely—just that strict TDD may not always be the most efficient approach.
4. "Tests lock you into suboptimal architectures"
→ How do tests lock you in? A change in code also changes tests.
While tests should evolve with the code, writing them too early can reinforce an initial architecture that may not be ideal. If a test suite is deeply coupled to the structure of the code before it has had time to evolve naturally, refactoring can become cumbersome, discouraging improvements. A more flexible approach is to allow the design to stabilize before committing to extensive test coverage.
5. "Good design comes from experience and critical thinking, not rigid test-first rules"
→ Good design, experience, and critical thinking have nothing to do with the process of writing the code.
I would argue that they absolutely do! Writing code is as much about making architectural and design decisions as it is about implementation. TDD is a technique that can influence design, but it should not be the primary driver of good architecture. Experience and critical thinking are necessary to understand when TDD helps and when it may introduce unnecessary constraints.
6. "Many bugs occur due to integration issues, which TDD does not directly address."
→ Is TDD for you only writing unit tests? TDD doesn't single out any form of testing, AFAIK.
You’re right that TDD can include multiple forms of testing, but in practice, many developers primarily apply it at the unit test level. Unit tests alone don’t catch real-world integration issues like misconfigured services, network failures, or database inconsistencies. That’s why integration, contract, and E2E tests are equally (if not more) important in some cases.
7. "Tests only check for expected behavior. They don’t catch unknown issues."
→ If the piece of code you are going to write can have multiple outcomes, you have to write tests for all the outcomes.
While tests can cover multiple known outcomes, they can’t always predict unknown issues—such as unexpected performance degradation, security vulnerabilities, or edge cases that weren’t anticipated during development. Techniques like fuzz testing, chaos engineering, and exploratory testing help catch issues that traditional TDD might miss.
8. "Requirements are still evolving" & "The code you write might change or be thrown away."
→ That is not an excuse to skip tests. That is why the tests exist—change the code how much you like, the outcome will be the same.
I’m not advocating skipping tests altogether, but in fast-moving projects, excessive early test-writing can slow down development. If a feature is still experimental or the product direction isn’t settled, spending too much time writing tests for code that will be drastically refactored (or discarded) can be inefficient. A balanced approach is to prioritize testing once the design becomes more stable.
I think we largely agree on the importance of testing, but my main point is that strict TDD is not always the best approach in every scenario. Testing should be a thoughtful process driven by project needs rather than a rigid rule to follow in all cases. Flexibility, experience, and critical thinking should guide the development process.
I really appreciate your insights and the discussion!
I agree we think alike, we both see the value of tests. On the other hand I would keep the idea of TDD in the strictest form as an ultimate boss level. Most of the time you are not going to play until the finish but you gave it your best shot.
I agree that not all tests are equal. Sometimes tests are a form of interactive documentation. And in my eyes they are as valuable as mission critical tests.
While I think fuzz testing, chaos engineering, and exploratory testing are good things, they require a lot of effort. When the test coverage is good it is easier to get to the cause of the unexpected behavior.
That is a problem of guidance. A unit test is also a misunderstood concept. For example if you have a private method that changes the outcome of a public method, the test can be an integration test due to the behavior of the public method. You mentioned yourself that UI-heavy app requires another sort of test, but it can still be a unit test.
While I do agree writing tests in the research fase of a project is counter-productive, once the project is out of that fase testing should start. If you change architectures on the fly, that is an indecision problem. You are not going to destroy your house because you think the foundation isn't good. Tests shouldn't stop you from changing architecture, they should give you pause on what you are going to do. We as developers love rewriting a bit too much.
TDD for me is not really about test first, but about the doing coding and testing in parallel. And that feels like the writing process is test driven.
Just finished up learning golang with Learn Go with Tests and along the way, I felt that TDD was definitely not something applicable when building out applications for production.
just how slow it is.
Thank you for sharing your thoughts and the scenarios where TDD bothers more than help, that's insightful!
Overall it is not a matter of "reasons why" or "reasons why not"; it is a matter of "when" and "how". I'll share a different "how" to exemplify what I mean:
The book "Pragmatic Programmer" (Dave Thomas, Andy Hunt, 1999) presents a technique called "Tracer Bullet", which is a curious kind of TDD (coding progress driven by a test): start with a hardcoded result and a successful test. Iteratively refactor the code (and sometimes the test) until the result is no longer hard-code, the goal is reached and you end up with an automated test as bonus.
The test can be anything that keeps confirming you are moving in the right direction: a CURL call running in a loop in a bash file, a separate file mocking data and importing your code, a postman test... Whatever your creativity comes up with. Maybe you will ditch it in the end, maybe reuse in your automated test suite... Sticking to a specific testing framework can also hinder your possibilities, so focus on ensuring the right track more than producing an automated test.
It is a "fake it until you make it" approach, or just "Green->Refactor" if you will. When it is applicable, the code design and automated tests beautifully emerge from it. Worth a try.
Appreciate you sharing that! The Tracer Bullet approach seems like a really cool way to keep testing flexible while guiding development. I like how it focuses on iteration rather than strict rules so it's definitely something worth trying more often. Thanks for the insight!
THANK YOU