skip to content

In Go, which kinds of type can hold nil, and does nil mean the same thing for each?

level: juniorimportance: must knowfreq 78%

answer

  1. count them - there are six
  2. nil is a zero value, not one value
  3. pointer, slice, map, chan, func, interface
  4. numbers, strings and structs never qualify
  5. each kind has its own panic rules

basics

~20 s

Six kinds of type can be nil: pointers, slices, maps, channels, functions and interfaces. nil is the zero value of each, not one shared value, so every kind has its own representation and its own rules about what you may safely do.

solid answer

~50 s

`nil` is a predeclared identifier, not a keyword, and it has no type of its own - it is the untyped zero value shared by pointer, slice, map, channel, function and interface types. That is why `var x = nil` does not compile: the compiler has no type to infer. The rules differ per kind. A nil slice is fully usable: `len`, `cap`, `for range` and `append` all work on it. A nil map reads fine and returns the zero value, but writing to it panics. A nil channel blocks forever on send and receive, and `close` on it panics. A nil func value panics when you call it. A nil pointer panics when you dereference it, though a pointer-receiver method that never dereferences can run on it. A nil interface panics on any method call. Numbers, strings, bools, structs and arrays can never be nil.

code

go · 10 lines
go
var p *int          // nil pointer
var s []string      // nil slice
var m map[string]int // nil map
var c chan int      // nil channel
var f func() error  // nil func value
var e error         // nil interface

// var x = nil      // compile error: nil has no default type

var n struct{ A int } // a struct is never nil

go deeper

for a junior

Be ready to list the six kinds of type that can be nil and to say that nil is their zero value. Know at least the headline rules: a nil slice is usable, writing to a nil map panics, calling a nil func value panics.

for a middle

Explain why the rules differ by pointing at the representations - a slice is a three-word header, a map is a pointer to a hash table, a func value is a pointer to code. Say what each one does on read, on write and on call.

for a senior

Show the design consequence: prefer types whose nil is usable, document whether a type's zero value is safe, and standardise on len(x) == 0 rather than x == nil so callers do not depend on which empty you happened to return.

for a principal

Frame it as an API contract question. Whether the zero value of an exported type is usable is a promise you cannot retract later, so decide it deliberately and write it in the doc comment rather than letting callers discover it from a panic.

## `nil` is a value, not a type `nil` is a **predeclared identifier** in Go's universe block, not a keyword. It has no type of its own: it is the *untyped* zero value shared by six kinds of type. Because it is untyped, `var x = nil` does not compile - the compiler has no default type to infer. You have to say which nil you mean: `var p *int`, `var s []string`, `var m map[string]int`, `var c chan int`, `var f func() error`, `var e error`. ## The six kinds of type that can be nil 1. **pointer** types (`*T`) 2. **slice** types 3. **map** types 4. **channel** types 5. **function** types 6. **interface** types Everything else has a non-nil zero value: `0` for numeric types, `""` for strings, `false` for bools, and for a struct or an array, each field or element set to its own zero. A struct is never nil, even when every field inside it is. ## What each nil actually is, and what you may do with it | kind | runtime representation | works on the nil value | panics | | --- | --- | --- | --- | | `*T` | address 0 | assignment, `== nil`, passing it around, calling a pointer-receiver method that never dereferences it | `*p`, `p.field` | | `[]T` | header: nil data pointer, len 0, cap 0 | `len`, `cap`, `for range` (zero iterations), `append`, `copy`, `== nil` | indexing, e.g. `s[0]` | | `map[K]V` | nil map pointer | `len`, `for range`, reading `m[k]` (zero value, `ok` false), `== nil` | any write `m[k] = v` | | `chan T` | nil channel | `== nil`; a send or a receive blocks forever rather than panicking | `close(c)` | | `func(...)` | nil code pointer | assignment, passing, `== nil` | calling it | | interface | a (type, value) pair that is (nil, nil) | assignment, `== nil` | calling any method on it | ## Why they differ They differ because the *representations* differ. A slice is a three-word header, so `len` reads a field that exists whether the data pointer is nil or not - which is why the idiomatic accumulator is `var out []string` followed by `append`, with no `make` in sight. A map value is one pointer to a runtime hash table: the lookup path checks for nil and hands back the zero value, while the assignment path has no table to write into and stops the program with `assignment to entry in nil map`. A func value is essentially a pointer to code, so calling a nil one jumps to address zero and the runtime reports `runtime error: invalid memory address or nil pointer dereference`. An interface value is a pair of words - a dynamic type and a value - and when both are absent there is no method table to dispatch through, so any method call panics. ## The nil receiver, which surprises newcomers A method with a pointer receiver is compiled as an ordinary function taking that pointer as its first argument, so a nil pointer is a perfectly legal argument. `func (n *node) size() int { if n == nil { return 0 } ... }` is a real, idiomatic Go pattern. The panic comes from the *body* dereferencing the pointer, not from the call itself. ## Practical consequences - **Prefer the usable nil where one exists.** A nil slice is a perfectly good empty list; writing `s := []string{}` "to be safe" adds nothing. A nil map is a perfectly good empty read-only lookup table. - **Guard where nil is not usable.** A map must be created with `make` (or a composite literal) before the first write. A channel must be created with `make`. A function-typed field must be checked or defaulted before it is called. - **Test emptiness with `len(x) == 0`, not `x == nil`.** They answer different questions, and for slices and maps the length test is the one callers usually mean. - **`nil` as an answer is rarely interesting on its own.** In review, the useful question is "what does absent mean for this field, and is its zero value usable?" - the six nils give six different answers. ## The one-sentence version Go has six nils, they are the zero values of six different runtime representations, and the only way to know whether a given nil is safe to use is to know which kind of nil it is.

  • Is nil a keyword in Go?
    No. It is a predeclared identifier in the universe block, like `true`, `len` or `int`. That means it can be shadowed by a local declaration named `nil`, and that it is untyped - which is why `var x = nil` fails to compile while `var x *int = nil` is fine.
  • Which of the six nils are safe to use without checking first?
    A nil slice, for `len`, `cap`, `range`, `copy` and `append`. A nil map, for `len`, `range` and reads. A nil pointer, if you only pass it around or call a pointer-receiver method written to tolerate it. The unsafe ones are writing to a nil map, calling a nil func value, calling a method on a nil interface, dereferencing a nil pointer, and closing a nil channel.
  • What is the zero value of a struct whose fields include a slice and a map?
    The struct itself is never nil - it exists as a value with every field set to that field's own zero. So its slice field is a nil slice and its map field is a nil map. The struct is usable immediately, the slice field is usable immediately, and the map field must be created with `make` before the first write.

nil is like the English word 'empty': an empty box, an empty chair and an empty promise are all 'empty', but you can sit on only one of them.

saying these in an interview costs you the question

  • Says nil is just a null pointer, as in other languages
  • Claims nil is a keyword with its own type
  • Thinks any nil value must be checked before every use
  • Believes ints, strings or structs can be nil
  • Says reading from a nil map panics
  • Assumes ranging over a nil slice panics