Golang nil checks: pointers, interfaces, slices, and empty structs

Tech reviewed: Deepak Prasad
Golang nil checks: pointers, interfaces, slices, and empty structs

In Go, nil is the zero value for pointers, slices, maps, channels, functions, and interfaces—it is related to what other languages call a null pointer, but the rules differ. People searching golang nil or golang check if nil usually need pointer checks, slice emptiness, optional structs via pointers, or interface edge cases. The sections below cover pointers (including errors), empty versus nil slices, maps and channels, why struct values are never nil, and the interface typed-nil trap. For returning and checking errors (non-nil error values), see returning errors in Go.

Tested with Go 1.24 on Linux.


Types whose zero value is nil

Only these kinds of values compare meaningfully to nil with == and !=: pointers (including unsafe.Pointer), slices, maps, channels, functions, and interfaces. Booleans, numbers, strings, arrays, and struct values have other zero values (false, 0, "", zeroed fields)—they are never nil themselves. The language spec on zero values spells out the defaults.


golang check if pointer is nil (golang null pointer)

For a pointer p, p == nil is the usual golang nil pointer check (what many people still describe as a golang null pointer check from C-style habits).

go
package main

import "fmt"

type Config struct{ Host string }

func main() {
	var cfg *Config
	alive := &Config{Host: "example.com"}
	fmt.Println("cfg is nil:", cfg == nil)
	fmt.Println("alive is nil:", alive == nil)
}
Output

You should see cfg is nil: true and alive is nil: false.

Errors and nil

The error interface follows the same idea: after _, err := strconv.Atoi("bad"), use if err != nil before using the result. The zero value for an error variable is nil; see Handle errors and the errors package.


check if slice is empty golang versus a nil slice

A nil slice has length 0, but a non-nil slice can also have length 0 ([]T{} or make([]T, 0)). For “no elements,” len(s) == 0 is the usual check if slice is empty golang pattern and is true for both cases. Use s == nil only when you care that the slice header itself is nil (for example JSON null versus [] sometimes matters at API boundaries).

go
package main

import "fmt"

func main() {
	var a []int              // nil slice
	b := []int{}             // empty, non-nil
	c := make([]int, 0)      // empty, non-nil
	fmt.Println("a len", len(a), "nil", a == nil)
	fmt.Println("b len", len(b), "nil", b == nil)
	fmt.Println("c len", len(c), "nil", c == nil)
	fmt.Println("all empty?", len(a) == 0 && len(b) == 0 && len(c) == 0)
}
Output

You should see length 0 for all three, nil true only for a, and all empty? true.


Maps and channels

var m map[string]int starts as a nil map: reading is safe, but writing panics until you assign a map from make or a literal. m == nil distinguishes “not allocated” from an empty but allocated map (len(m) == 0 can be true for both). Channels behave similarly: var ch chan int is nil; ch == nil is the idiomatic check before a non-blocking receive or close.


golang check if struct is empty (structs are never nil)

You cannot write var v MyStruct; v == nil—a struct value is never nil. For check if struct is empty golang (same intent as golang check if struct is empty) you usually mean “all fields are zero”: compare important fields, or use reflect.ValueOf(v).IsZero() for a generic test (handles nested structs for exported fields as documented in reflect).

To represent “no value,” use a pointer: var cfg *Config; cfg == nil is valid and common for optional configuration.


golang check if interface is nil (typed nil trap)

An interface value holds a dynamic type and a dynamic value. var v any is a true nil interface: v == nil is true. If you assign a typed nil pointer into that interface, the dynamic type is no longer “absent,” so v == nil becomes false even though the pointer inside is nil:

go
package main

import "fmt"

func main() {
	var i any
	fmt.Println("zero interface:", i == nil)
	var p *int
	i = p
	fmt.Println("interface holding typed nil *int:", i == nil)
}
text
zero interface: true
interface holding typed nil *int: false

That behavior is a frequent source of bugs for people learning golang check if interface is nil. Practical fixes: keep a concrete *T and test p == nil before converting to any, or document that callers must check the concrete type. The Go FAQ entry on nil errors discusses the same underlying idea for error values.


Summary

Golang nil applies to pointers, slices, maps, channels, functions, and interfaces—not to struct, string, or numeric values. For a golang nil pointer or golang null pointer style check, use p == nil. For check if slice is empty golang, prefer len(s) == 0; use s == nil when the distinction between nil and empty slices matters. Structs are never nil; golang check if struct is empty means zero fields or a *Struct that may be nil. For interfaces, == nil is only true when both type and value are unset—assigning a typed nil into an any or error breaks the naive check, so test the concrete value or design APIs to avoid hiding typed nils behind interfaces.


References


Frequently Asked Questions

1. Can a struct be nil in Go?

A struct value is never nil; only pointers, slices, maps, channels, functions, and interfaces use nil. Use *MyStruct == nil for absence, or reflect.ValueOf(v).IsZero() to test an all-zero struct value.

2. How do I check if a slice is empty in golang?

Use len(s) == 0 for “no elements”; that is true for both a nil slice and a non-nil empty slice. Use s == nil only when you specifically care that the slice header is nil.

3. Why is my interface variable not nil after I assigned nil?

An interface value holds a type and a value. Assigning a typed nil pointer (for example var p *T; var i any = p) sets the dynamic type to *T, so comparing i == nil is false even though p is nil. Compare or branch on the concrete pointer before putting it in an interface, or use reflection with care.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops …

  • Deep Learning with TensorFlow
  • Machine Learning with Python
  • Go (programming language)
  • Python (programming language)
  • Java (programming language)
  • MongoDB