# The Tracer Bullet Approach in Test-Driven Development Explained

> Master the tracer bullet approach in TDD. Write one test at a time, implement minimal code, and repeat for vertical slices. Optimize your development workflow.

- Repository: [Matt Pocock/skills](https://github.com/mattpocock/skills)
- Tags: deep-dive
- Published: 2026-04-04

---

**The tracer bullet approach is a TDD technique where you write one test at a time, implement the minimal code to make it pass, and repeat—creating vertical slices through your system rather than horizontal layers.**

The tracer bullet approach offers a concrete implementation of the classic red-green-refactor loop described in the mattpocock/skills repository. Instead of writing batches of tests upfront, you fire a single "tracer" test through every layer of your application to verify the execution path works end-to-end. This method ensures every piece of code you write is justified by observable behavior rather than imagined specifications.

## What Is the Tracer Bullet Approach in TDD?

In [`tdd/SKILL.md`](https://github.com/mattpocock/skills/blob/main/tdd/SKILL.md), the tracer bullet is defined as the first test you write when beginning a new feature. It acts as a probe that confirms the path you are about to build actually works, cutting vertically through every architectural layer—from API to database—rather than slicing horizontally across one layer at a time.

### Vertical Slices vs. Horizontal Slices

The skill file warns against **"horizontal slicing,"** where developers write all tests first and then add all implementation later. According to the source in `tdd/SKILL.md#L18-L27`, this anti-pattern leads to "crap tests" that verify imagined rather than real behavior because they are disconnected from the actual implementation constraints encountered during coding.

### The First Test as a Probe

The initial test is explicitly called the *tracer bullet* in the documentation at `tdd/SKILL.md#L60-L69`. It serves as a proof-of-concept that your architecture can support the desired functionality end-to-end before you invest in additional test coverage.

## The Tracer Bullet Workflow

Following the iterative loop described in `tdd/SKILL.md#L71-L80`, the tracer bullet approach follows these five steps:

1. **Plan** – Identify the public interface and the specific behaviors you need.
2. **Tracer-bullet test** – Write a single failing test expressing one desired behavior.
3. **Green** – Implement the smallest amount of code necessary to make that test pass.
4. **Refactor** – Clean up the implementation without breaking the passing test.
5. **Repeat** – Add the next tracer-bullet test and continue the cycle.

Each iteration creates a thin vertical slice that proves the complete execution path works, ensuring you never write code for a test that later proves irrelevant.

## Why Tracer Bullets Matter for TDD

The mattpocock/skills repository identifies three primary advantages to this approach:

- **Feedback-driven design** – Each test tells you exactly what the system should do, allowing you to shape the public API incrementally based on real requirements.
- **Safety during refactor** – Because tests depend only on public interfaces, they remain green even when internal structures change, providing a safety net.
- **Reduced waste** – You avoid writing unnecessary code since the system evolves only as dictated by passing tests, eliminating speculative implementations.

## Tracer Bullet Approach Example in TypeScript

Here is a minimal example demonstrating the pattern. First, the tracer-bullet test verifies that creating a user can be retrieved through the public API:

```typescript
// tests/user.test.ts
test("createUser returns a retrievable user", async () => {
  const created = await createUser({ name: "Alice" });
  const fetched = await getUser(created.id);
  expect(fetched.name).toBe("Alice");
});

```

Then, the minimal implementation to make this test pass:

```typescript
// src/user.ts
type User = { id: string; name: string };
const store = new Map<string, User>();

export async function createUser(data: { name: string }): Promise<User> {
  const id = `u-${Date.now()}`;
  const user = { id, name: data.name };
  store.set(id, user);
  return user;
}

export async function getUser(id: string): Promise<User | undefined> {
  return store.get(id);
}

```

With the test now passing, you can refactor—perhaps extracting a repository class or adding validation—while keeping the test green. Subsequent tracer-bullet tests would cover additional behaviors such as updating users or handling duplicates.

## Summary

- The tracer bullet approach in TDD involves writing one test at a time and implementing just enough code to pass it, creating vertical slices through your system.
- This technique avoids "horizontal slicing," which produces brittle tests that verify imagined rather than real behavior according to [`tdd/SKILL.md`](https://github.com/mattpocock/skills/blob/main/tdd/SKILL.md).
- Each tracer bullet acts as a probe confirming end-to-end functionality before additional complexity is added.
- The workflow follows the classic red-green-refactor loop: plan, test, implement, refactor, repeat.
- Tests written this way depend on public interfaces, ensuring they remain stable during internal refactoring.

## Frequently Asked Questions

### What is the difference between tracer bullets and horizontal slicing?

**Tracer bullets create vertical slices** through every layer of the system (API, business logic, data access) with each test-code pair, whereas horizontal slicing involves writing all tests for one layer before implementing any code, leading to disconnected specifications. As noted in `tdd/SKILL.md#L18-L27`, horizontal slicing results in fragile tests that do not reflect actual implementation constraints.

### Why is the first test called a tracer bullet?

The term draws from ammunition used to visualize bullet trajectory—**it acts as a probe** that confirms the path you are about to build actually works. According to `tdd/SKILL.md#L60-L69`, this first test proves your architecture can support the feature end-to-end before you commit to full implementation.

### How does the tracer bullet approach prevent "crap tests"?

By requiring tests to drive implementation one behavior at a time, the approach ensures tests verify **observable behavior rather than internal implementation details**. The source at [`tdd/SKILL.md`](https://github.com/mattpocock/skills/blob/main/tdd/SKILL.md) warns that writing all tests upfront produces "crap tests" that verify imagined behavior, whereas tracer bullets ensure each test reflects real, executable functionality.

### Can I use tracer bullets with existing codebases?

Yes. While particularly valuable for new features, you can apply the tracer bullet approach to existing code by writing a single test for the next desired behavior or refactoring, implementing the minimal change, and repeating. This maintains the safety net of tests that depend only on public interfaces, even when working with legacy systems.