skip to content

What does the declaration var _ io.Writer = (*Blob)(nil) do in a Go source file?

level: middleimportance: should knowfreq 44%

answer

  1. the blank identifier throws it away
  2. a typed nil pointer, never dereferenced
  3. the check is purely at compile time
  4. it breaks the build at the definition
  5. Go's closest thing to implements

basics

~20 s

It is a compile-time assertion that *Blob satisfies io.Writer. If a method is missing or a signature drifts, the build fails on that line instead of in some consumer's package. It costs nothing at run time.

solid answer

~50 s

It is the standard Go idiom for making an implicit interface relationship explicit to the compiler. `(*Blob)(nil)` is a typed nil pointer converted to `*Blob`; assigning it to a variable of type `io.Writer` forces the compiler to check `*Blob`'s method set against `io.Writer` right there. The blank identifier `_` discards the value, so nothing is stored, nothing is allocated, and no method is ever called on the nil pointer — the whole thing evaporates after type checking. You place it in the file that declares the type, so that a refactor which drops or renames `Write` breaks the build at the definition rather than downstream in whoever imports you. It also documents intent: a reader sees immediately that this type is meant to be usable as an `io.Writer`, which no other part of Go syntax records.

code

go · 10 lines
go
type Blob struct{ data []byte }

// Compile-time assertion: if Write ever stops matching io.Writer,
// the build fails here rather than in a consumer.
var _ io.Writer = (*Blob)(nil)

func (b *Blob) Write(p []byte) (int, error) {
	b.data = append(b.data, p...)
	return len(p), nil
}

go deeper

for a junior

Recognise the line when you see it in a codebase and know it is a compiler check, not running code. Know that the underscore means the value is thrown away and that nothing happens at run time.

for a middle

Explain each piece: the conversion producing a typed nil pointer, the assignment to an interface-typed variable forcing the method-set check, and the blank identifier discarding the result. Say where you would place the line and why.

for a senior

Argue when the assertion is worth adding — exported types whose interface role is part of the contract — and when it is noise. Be able to describe the failure it prevents: a refactor that leaves your package green and breaks a consumer's build.

for a principal

Own it as a policy question: which relationships in a shared library are load-bearing enough to pin at the definition, and whether asserting in _test.go instead is worth trading build-time detection for a lighter import graph.

## The idiom ```go var _ io.Writer = (*Blob)(nil) ``` Read it right to left. `(*Blob)(nil)` is a **conversion**: it produces the nil value of pointer type `*Blob`. The parentheses around `*Blob` are required so the compiler does not parse the expression as a dereference. That value is then assigned to a variable declared with type `io.Writer`, which is exactly the situation in which Go checks interface satisfaction. The variable's name is the blank identifier `_`, so the value is thrown away and no package-level variable is created. The result is a **compile-time assertion**. It produces no code, no data and no initialisation work. If `*Blob`'s method set covers `io.Writer`, the line compiles to nothing. If it does not, the compiler stops with an error on that line, naming the method that is missing or whose signature does not match. ## Why it exists Go's interface satisfaction is implicit: nothing in a type's declaration records which interfaces it is intended to satisfy, and the compiler only checks when a value is actually used as that interface. If a package declares a type whose entire purpose is to be handed to callers as an `io.Writer`, but never itself assigns it to an `io.Writer`, then the package can be refactored into non-satisfaction and still build and test green. The mismatch surfaces later, in someone else's package, at someone else's line number. The assertion pins the relationship at the definition. It is the closest Go gets to `implements`, and it is deliberate that you opt into it rather than getting it for free — it is a statement about intent, not about types. ## Where to put it Conventionally, immediately above or below the type declaration in the same file, so a reader and a reviewer see the intent together with the type. Some packages group them near the top of the file. If you would rather not add the interface's package as a real import of your production code, put the assertion in the package's `_test.go` file instead: the check still runs on `go test` and `go vet`, and the import stays out of the built binary's dependency graph. The trade-off is that a plain `go build` no longer catches the break. ## Variants you will see - `var _ io.Writer = (*Blob)(nil)` — the most common form; asserts the pointer type. - `var _ io.Writer = Blob{}` — asserts the value type instead, which is what you want when the type is meant to be used as a value. - `var _ = []io.Writer{(*Blob)(nil), (*Cache)(nil)}` or several separate lines — several assertions in one place, common in a package with many implementations of the same shape. - Named rather than blank (`var _blobIsWriter io.Writer = ...`) — avoid; it creates a real package-level variable and buys nothing. Using a typed nil is the point: it costs no allocation and constructs nothing. Writing `var _ io.Writer = &Blob{}` would work as a check but allocates a real value at package initialisation for no reason, and would run any constructor logic the type happens to need. ## What it does not do - It does not make the type satisfy the interface. Satisfaction comes from the methods; the assertion only checks. - It is not a runtime check. Nothing is dereferenced, and the nil pointer inside is never used. - It does not stop the type from satisfying other interfaces, and it does not narrow the type in any way. - It does not help if the interface itself is the thing that changed — if `io.Writer` gained a method, the assertion breaks the build, which is the desired outcome, but it does not repair anything. ## In review A useful house rule for a reviewer: any **exported** type whose documented purpose includes "can be used as an X" should carry the assertion for X, because that relationship is part of the package's public contract and nothing else in the language records it. For unexported types used as an interface inside the same package, the assertion is usually redundant — the package's own code already assigns them and the compiler already checks.

  • Why use a typed nil pointer rather than &Blob{} on the right-hand side of the assertion?
    `(*Blob)(nil)` constructs nothing: it is a conversion the compiler resolves during type checking, and the blank identifier discards the result. `&Blob{}` would allocate a real value during package initialisation and run whatever the zero value implies, for a check that needs no value at all.
  • Can you put the assertion in a _test.go file instead of the package's production source?
    Yes, and it is a common choice when you do not want the interface's package imported by the built binary. The check then runs under `go test` rather than `go build`, so a plain build of the package will no longer catch a dropped method — a real trade-off, not a free move.
  • When is the assertion redundant?
    When the package itself already uses the type as that interface — passing it to a function taking `io.Writer`, storing it in a field of interface type. The compiler is already checking at those sites. The assertion earns its place for exported types whose only interface use is in a consumer you cannot see.

saying these in an interview costs you the question

  • Thinks the line allocates a value or runs at init
  • Believes the nil pointer is dereferenced at run time
  • Says the assertion is what makes the type satisfy the interface
  • Confuses it with a runtime type assertion on an interface value
  • Writes &Blob{} because a real value seems necessary