Test-driven development runs a short cycle: write a failing test (red), make it pass with the simplest change (green), then improve the design while the tests stay green (refactor). The order is the point, because each step answers a different question.
Before you start
You should be comfortable writing tests and small functions. This article covers the cycle and its discipline.
Step-by-step walkthrough
Step 1: Start red
Write a test for the behavior you want and run it to see it fail. A failing test proves the test can fail, which rules out a test that passes for the wrong reason. It also forces you to define the expected behavior before the implementation.
Step 2: Make it green minimally
Write the least code that passes the test, even if it is not elegant. The goal is a quick feedback loop and a known-good state. Over-engineering before the test passes hides which change made it work.
Step 3: Refactor under green
With the tests passing, improve names, remove duplication and adjust the design, re-running the tests after each step. A green suite means the refactor preserved behavior, which is exactly when it is safe to change structure.
Worked scenario
The cycle produces a tested function incrementally.
red: test('empty cart total is 0', () => expect(total([])).toBe(0)); // fails
green: function total(items) { return 0; }
red: test('one item total is its price', () => expect(total([{price: 5}])).toBe(5)); // fails
green: function total(items) { return items.reduce((s, i) => s + i.price, 0); }
refactor: extract and rename without breaking the green testsWalk through the example
Each red step defines the next behavior, and each green step satisfies it minimally, so the implementation grows only as tests demand. The refactor happens while everything passes, so a regression is caught immediately. The tests document the behavior as it was built.
Common mistake
Writing tests after the code to match what it does, which tests the implementation rather than the requirement and misses cases the code forgot. Another is skipping the refactor step, so the design stays rough even with tests.
Verify the behavior
Confirm each new test fails before the implementation exists. Check that making it pass required the smallest change. After refactoring, confirm the same tests still pass without edits, showing behavior was preserved.
Interview exercise
Why insist the test fails first?
Answer and reasoning
A test that has never failed might pass for the wrong reason, such as a mistaken assertion or a test that does not exercise the code. Seeing it fail proves the test is wired to the behavior and can detect a regression. It also forces you to specify the expected behavior before writing the implementation.
Continue learning
Compare structure in Arrange, act, assert and design in Test boundaries. Read the Martin Fowler on TDD and try the Testing interview questions.