Right, let’s talk about one of the most reliably annoying little handshakes between Go’s type system and the messy reality of your database: NULL. Go has a wonderful, strict, “zero-value” philosophy. A string is never nil; it’s just an empty string "". The database world, however, is a land of ambiguity and forgotten data. A VARCHAR column can absolutely be NULL, meaning “this value is intentionally unknown.” It’s not empty, it’s not zero, it’s nothing. And Go despises this kind of ambiguity.

So, what happens when you SELECT NULL into a string? Panic? Corruption? Silent failure? Thankfully, the database/sql package is smarter than that. It protects you by forcing you to deal with the possibility. Enter sql.NullString. It’s not a magical new type; it’s a simple struct, and understanding its guts is key to not hating it.

type NullString struct {
    String string
    Valid  bool // Valid is true if String is not NULL
}

It’s a box. You open the box (Valid = true), and inside is your String. Or the box is empty (Valid = false), and the String inside is just its zero value and should be ignored.

The Right Way to Scan and Use It

You don’t magically turn a string into a NullString. You let the database driver do the work during scanning. Here’s how you use it correctly in a query:

var name sql.NullString
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 123).Scan(&name)
if err != nil {
    log.Fatal(err)
}

if name.Valid {
    fmt.Printf("Hello, %s!\n", name.String) // Use the actual value
} else {
    fmt.Println("Hello, stranger!") // Handle the NULL case
}

The crucial part is that you always check .Valid before you use .String. Treating .String as the truth when .Valid is false is a classic rookie mistake. It’s like drinking from an empty glass and pretending it’s water.

Inserting or Updating with NULL

The process also works in reverse. If you want to insert a NULL into the database, you don’t send nil for a string. You send a NullString where Valid is false.

// This user explicitly has no middle name, insert a NULL
_, err := db.Exec(`
    INSERT INTO users (first_name, middle_name, last_name)
    VALUES (?, ?, ?)
`, "Jane", sql.NullString{Valid: false}, "Doe")

// This user has a middle name, insert the value
_, err = db.Exec(`
    INSERT INTO users (first_name, middle_name, last_name)
    VALUES (?, ?, ?)
`, "John", sql.NullString{String: "Quincy", Valid: true}, "Doe")

The Annoying Part (I Told You I’d Be Honest)

Yes, it’s verbose. Yes, it feels like boilerplate. And yes, there are sql.NullInt64, sql.NullFloat64, sql.NullBool, and so on, each with their own nearly identical struct layout. It’s a bit of a slog. The designers of database/sql made a very deliberate, if cumbersome, choice: type safety and explicit handling over convenience. They force you to acknowledge the existence of NULL every single time. It’s the friend who won’t let you just “assume everything will be fine.”

Alternatives for the Lazy (and Smart)

If the verbosity is killing you, you have a couple of escape hatches, each with trade-offs.

1. Use Pointer Types: You can scan NULL into a *string.

var name *string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 123).Scan(&name)
if name != nil {
    fmt.Printf("Hello, %s!\n", *name)
} else {
    fmt.Println("Hello, stranger!")
}

This is less code, but now you’re dealing with pointers and nil dereference panics if you’re not careful. I often find this more dangerous than the explicit Valid check.

2. Use a Scanner Wrapper (Advanced): The sql package requires types to implement the sql.Scanner interface. You can create your own custom type that handles this more elegantly. This is more work upfront but can make your domain code much cleaner.

The Bottom Line: sql.NullString is your reliable, if slightly boring, bodyguard. It makes the uncertainty of your data explicit in your code. While it adds a few lines, it prevents a whole class of subtle bugs where you’re treating “unknown” as “empty.” Embrace the verbosity. Check .Valid. Your future self, debugging a weird data issue at 2 AM, will thank you for it.