The string value is never nil
You are parsing a configuration file. The name field looks blank. You grab the value and check if it is empty. Your muscle memory from JavaScript screams if (!name). Your Python brain whispers if not name:. You write if name == nil in Go, hit save, and the editor turns red. The compiler rejects the program with invalid operation: name == nil (mismatched types string and untyped nil).
Strings in Go are never nil. They are values. If a string has no content, it is the empty string "", not nothingness. This distinction removes a whole class of null-pointer bugs. You can call methods on a string variable without checking for nil first. The empty string is just a string with length zero.
How Go represents strings
Go treats strings as immutable sequences of bytes. Under the hood, a string is a small struct containing a pointer to a byte array and a length field. When you declare var s string, the variable holds the zero value immediately. The zero value of a string is "". The pointer points to a read-only empty byte slice, and the length is zero.
This design means strings are safe to use everywhere. You can pass them to functions, store them in structs, or return them from methods without worrying about uninitialized state. The compiler guarantees that a string variable always contains a valid string value. If you need an optional string, you must use a pointer type *string. A pointer can be nil or point to a string. The string itself is never nil.
Convention aside: receiver names in Go are usually one or two letters matching the type. If you define a method on a struct holding a string, name the receiver s, not this or self. For example, (s *Config) GetName() string. This keeps code concise and matches the standard library style.
Minimal emptiness check
Here is the simplest check: compare the string to the empty literal.
package main
import "fmt"
func main() {
s := ""
// Direct comparison checks content against the empty string.
// This is the idiomatic way to check for emptiness.
if s == "" {
fmt.Println("Empty via comparison")
}
// Length check works too but reads like counting.
// The compiler optimizes both to the same machine code.
if len(s) == 0 {
fmt.Println("Empty via length")
}
}
# output:
Empty via comparison
Empty via length
What happens at runtime
When you write s == "", the compiler generates code that checks the length field of the string header. If the length is zero, it returns true. It does not iterate over bytes. The check is O(1). The len(s) == 0 check does the exact same thing. The compiler recognizes both patterns and emits identical assembly. Performance is identical.
The choice is purely about readability. s == "" reads like English: "is s empty?". len(s) == 0 reads like a math operation. Go developers prefer the direct comparison because it expresses intent clearly. The community accepts this style because it makes the code self-documenting. You do not need to explain why you are checking the length. The comparison says it all.
Strings are values. The empty string is a value. Treat it like any other value.
Handling whitespace and user input
Real code often deals with user input. A user might type spaces instead of a name. The string " " is not empty. It contains three space characters. The check s == "" returns false. If you want to treat whitespace-only strings as empty, you must trim the whitespace first.
Here is a validation function that handles blank input correctly.
package main
import (
"fmt"
"strings"
)
// ValidateName checks if the input is usable.
// It rejects empty strings and whitespace-only inputs.
func ValidateName(input string) error {
// TrimSpace removes leading and trailing whitespace.
// This catches cases like " " which are effectively empty.
cleaned := strings.TrimSpace(input)
// Check the cleaned value against the empty string.
if cleaned == "" {
return fmt.Errorf("name cannot be empty")
}
return nil
}
func main() {
// Test with a blank string.
err := ValidateName("")
fmt.Println(err)
// Test with whitespace only.
err = ValidateName(" ")
fmt.Println(err)
// Test with valid input.
err = ValidateName("Alice")
fmt.Println(err)
}
# output:
name cannot be empty
name cannot be empty
<nil>
strings.TrimSpace allocates a new string. If you are processing huge strings in a tight loop, this allocation adds up. For most validation scenarios, the cost is negligible. If performance is critical, you can iterate over the bytes manually to check for non-whitespace characters without allocating. For 99% of code, strings.TrimSpace is the right tool.
Convention aside: error handling in Go is explicit. The pattern if err != nil { return err } is verbose by design. The community accepts the boilerplate because it makes the unhappy path visible. You cannot accidentally swallow an error. Every caller must decide what to do with the result.
Trim before checking. Whitespace is data until you say otherwise.
Pointers and optional strings
If you need to distinguish between "empty string" and "no value provided", use a pointer. A *string can be nil. This is common in JSON unmarshaling or database queries where a field might be missing.
Here is how to handle an optional string pointer safely.
package main
import "fmt"
func main() {
// A pointer to a string can be nil.
var ptr *string = nil
// Check the pointer first to avoid a panic.
// Dereferencing a nil pointer causes a runtime panic.
if ptr == nil {
fmt.Println("Pointer is nil")
}
// Assign a string to the pointer.
name := "Bob"
ptr = &name
// Now the pointer is valid. Check the value.
if *ptr == "" {
fmt.Println("String is empty")
} else {
fmt.Println("String has value:", *ptr)
}
}
# output:
Pointer is nil
String has value: Bob
If you dereference a nil pointer, the program crashes with runtime error: invalid memory address or nil pointer dereference. Always check ptr == nil before accessing *ptr. This is the only place where nil matters for strings. The string value itself remains a value type.
Pointers can be nil. Strings cannot. Know the difference.
Unicode traps and invisible characters
Go strings are byte sequences. The len function returns the number of bytes, not the number of characters. The string "café" has length 5 because the é is two bytes in UTF-8. The string "🚀" has length 4.
Checking len(s) == 0 is safe for emptiness. An empty string has zero bytes and zero runes. However, checking len(s) > 0 does not guarantee visible content. A string might contain only zero-width spaces or other invisible Unicode characters. strings.TrimSpace handles ASCII whitespace and common Unicode spaces, but it does not catch every invisible character.
If you need to detect truly blank content including invisible characters, you might need a more sophisticated check. For most applications, strings.TrimSpace(s) == "" is sufficient. If you are processing untrusted text where invisible characters matter, consider using unicode.IsSpace in a loop or a dedicated sanitization library.
Convention aside: public names start with a capital letter. Private names start lowercase. There are no keywords like public or private. If you export a function IsEmpty, it starts with a capital. If it is internal, use isEmpty. This convention controls visibility across packages.
Emptiness is zero bytes. Unicode complexity starts after that.
Decision matrix
Use s == "" when you want the idiomatic, readable check for an empty string. Use len(s) == 0 when you are already computing the length for another reason and want to reuse the value. Use strings.TrimSpace(s) == "" when whitespace-only strings should count as empty. Use ptr == nil when the string is optional and represented as a pointer. Use manual iteration when you need to detect invisible Unicode characters that TrimSpace misses.
Readability wins. s == "" is the Go way.
Where to go next
- How to Normalize Unicode Strings in Go
- [How to Pad a String in Go (Left and Right)]/en/how-to-pad-a-string-in-go-left-and-right/)
- How to Replace Part of a String in Go