The Trap of nil in Go Interface
Grace Collins
Solutions Engineer · Leapcell

During the process of coding in Go, we may encounter a situation where, when using an interface type parameter (such as any
or interface{}
) to receive other parameters, the given parameter value is clearly nil
, yet the inequality check x == nil
does not hold. Why is this the case? This article will reveal the mystery.
Case Study
package main import "fmt" func main() { var a any = nil var b *int = nil fmt.Println("isNil: ", isNil(a)) fmt.Println("isNil: ", isNil(b)) fmt.Println("b == nil: ", b == nil) } func isNil(x any) bool { return x == nil }
Program output:
isNil: true
isNil: false
b == nil: true
The Go code example above demonstrates some subtle differences when using the any
type (i.e., interface{}
) to determine whether a variable is nil
. Specifically, it shows why, in some cases, a variable of type any
is not considered nil
even if its value is nil
.
In this code sample, an isNil
function is defined with a parameter x any
. Then, in the main
function, two variables a
and b
of different types are initialized, both assigned the value nil
. Next, they are passed as arguments to the isNil
function. Additionally, the result of b == nil
is printed directly for comparison.
From the output, we can see that inside the isNil
function, a == nil
evaluates to true
, while b == nil
evaluates to false
. However, outside the function, b == nil
evaluates to true
. This is because within the isNil
function, the parameter is of type any
, which causes x == nil
to evaluate as false
. To understand the specific reason, we need to look at the internal structure of any
. Details below.
The Internal Structure of any (interface{})
In Go, any
is an alias for interface{}
. Internally, an interface{}
value consists of two parts: the type part and the value part.
- Type part: The concrete type held by the interface. If the interface holds a value of type
*int
, then the type part is*int
. - Value part: The actual value held by the interface. If the value is the integer
3
, then this part is3
; if it’snil
, then it’snil
.
When a value is assigned to an interface type (like any
), the interface stores both the type and the actual value. Only when both the type part and the value part are nil
is the interface considered to be nil
.
Going back to the earlier code example, when the value of variable b
is assigned to an interface variable x
, the internal structure of x
becomes type = *int
and value = nil
. Therefore, x == nil
evaluates to false
.
Checking for nil Using Reflection
Since ==
or !=
cannot always reliably determine whether an interface type is nil
, how can we solve this problem? The answer is: by using reflection.
With reflection, we can directly check whether the value of a variable is nil
.
func isNil(x any) bool { if x == nil { return true } value := reflect.ValueOf(x) switch value.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: return value.IsNil() default: return false } }
Summary
This article delves into why, when using Go, a variable of interface type may not be considered nil
even when its value is nil
. Through specific code examples and an analysis of the internal structure of any
(i.e., interface{}
), we uncover the essence of this phenomenon.
Key takeaways:
- Interface type internal structure: An
any
(i.e.,interface{}
) is composed of a type part and a value part at the underlying level. An interface is considerednil
only when both parts arenil
. - Solution: Use reflection to accurately determine whether an interface-type variable is
nil
.
We are Leapcell, your top choice for hosting Go projects.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage — no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ