T Error Vs T Fatal
Fast recall
Quick revision
Core answer
Whether you understand T Error Vs T Fatal beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
The behavioral difference, executed:
func TestError(t *testing.T) {
t.Error("first problem") // test marked FAIL — execution continues
t.Error("second problem") // ALSO shown — you see BOTH
}
func TestFatal(t *testing.T) {
t.Fatal("fatal — stop") // FAIL — this test stops NOW
t.Error("never reached") // not executed
}The choice is about the test's semantics:
Use t.Error when the checks are INDEPENDENT — you want to see ALL the failures in one run (a validation test with five field checks: report all five, fix them all).
Use t.Fatal when subsequent checks DEPEND on earlier ones — a test that sets up state, then checks: if the setup check failed, the rest are noise (and might panic on nil). Fatal is the guard-clause for test logic.
The subtest interaction: t.Fatal inside a t.Run subtest stops only that SUBTEST (the subtest's goroutine exits — the parent and sibling subtests continue). t.Error in a subtest marks that subtest failed — same isolation.
The runtime mechanism: t.Fatal calls runtime.Goexit() — terminates the test goroutine cleanly (running deferred functions). That's why Fatal inside a helper function (not the test itself) still works — it exits whatever goroutine called it — but it's why Fatal inside a goroutine you spawned (not the test's) does nothing useful (exits the spawned goroutine, not the test).
T Error Vs T Fatal
Direct Answer
t.Error: marks the test FAILED but CONTINUES executing (you see all subsequent errors in this test). t.Fatal: marks FAILED and STOPS this test immediately (goroutine exits via runtime.Goexit).
Deep Dive & Architecture
The runtime.Goexit mechanics: t.Fatal marks the test failed, then calls Goexit — the goroutine-local exit that runs deferred functions but doesn't panic other goroutines. The test framework's runner detects the exit, records the result, moves to the next test. This is why Fatal in a helper is safe (the helper's goroutine is the test's goroutine) but in a spawned goroutine is a trap (exits the spawned one — the test continues, unaware).
The Error accumulation model: each t.Error/t.Errorf appends to the test's failure buffer and sets the failed flag. At the test's end, the framework prints ALL accumulated messages. That's the multiple-failures view — you get the full diagnosis in one run.
The parallel-test wrinkle: t.Parallel() means subtests run concurrently — each has its own goroutine. Fatal/Error semantics are per-goroutine (the subtest's), not global. A parent's Fatal while parallel subtests are running is unusual (the parent's Run has already returned control — the framework waits for parallel subtests at the end). The mental model stays goroutine-scoped — which IS the test-scoped behavior for the common cases.
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use T Error Vs T Fatal in a real project?
- What is a common mistake when using T Error Vs T Fatal?
- How would you compare T Error Vs T Fatal with an alternative?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.