Go Tutorial: Go Blank Identifier _ — Master Tutorial

The underscore:

_

is called the blank identifier in Go.

The easiest way to understand it is:

_ means: “Go gives me this value, but I intentionally do not want to use it.”

It is not a normal variable. You can put values into _, but you cannot get values back out of _.


1. Why Does Go Need _?

Go is strict about unused variables.

This code will not compile:

package main

func main() {
    name := "Rajesh"
}

Go gives an error similar to:

declared and not used: name

Go wants you to either:

  1. use the value, or
  2. explicitly tell Go that you want to ignore it.

That is one major purpose of _.


2. Most Common Use: range Loops

Consider:

menu := []string{"Pizza", "Burger", "Pasta"}

When you use range on a slice, Go can return two values:

for index, value := range menu {
}

Example:

package main

import "fmt"

func main() {
    menu := []string{"Pizza", "Burger", "Pasta"}

    for index, item := range menu {
        fmt.Println(index, item)
    }
}

Output:

0 Pizza
1 Burger
2 Pasta

So:

range menu
   │
   ├── index
   │
   └── value

3. Ignore the Index with _

Suppose you only care about the food name.

Instead of:

for index, item := range menu {

use:

for _, item := range menu {

Example:

package main

import "fmt"

func main() {
    menu := []string{"Pizza", "Burger", "Pasta"}

    for _, item := range menu {
        fmt.Println(item)
    }
}

Output:

Pizza
Burger
Pasta

Meaning:

for _, item := range menu {

can be read as:

for IGNORE_INDEX, item := range menu

or mentally:

I don't care, item
       ↓       ↓
       _      item

4. If You Only Need the Index

Interestingly, if you only need the index, you don’t need _.

You can write:

for index := range menu {
    fmt.Println(index)
}

Output:

0
1
2

You could technically write:

for index, _ := range menu {

but that is unnecessary.

Prefer:

for index := range menu {

5. _ with Functions Returning Multiple Values

This is another extremely common use.

Suppose a function returns two values:

func calculate() (int, int) {
    return 10, 20
}

Normally:

a, b := calculate()

Example:

package main

import "fmt"

func calculate() (int, int) {
    return 10, 20
}

func main() {
    a, b := calculate()

    fmt.Println(a)
    fmt.Println(b)
}

Output:

10
20

But suppose you only want the first value.

Use:

a, _ := calculate()

Full example:

package main

import "fmt"

func calculate() (int, int) {
    return 10, 20
}

func main() {
    a, _ := calculate()

    fmt.Println(a)
}

Here:

calculate()
     │
     ├── 10 → a
     │
     └── 20 → _
              ignored

6. _ with Go Errors

Go functions frequently return:

value, error

For example:

number, err := strconv.Atoi("100")

strconv.Atoi() converts a string to an integer.

Example:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    number, err := strconv.Atoi("100")

    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Println(number)
}

Output:

100

Sometimes you may see:

number, _ := strconv.Atoi("100")

This means:

number → keep
error  → ignore

Example:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    number, _ := strconv.Atoi("100")

    fmt.Println(number)
}

However, be careful.

Bad practice

number, _ := strconv.Atoi(input)

If input could be invalid, you are throwing away useful error information.

Usually prefer:

number, err := strconv.Atoi(input)

if err != nil {
    fmt.Println("Invalid number")
    return
}

Important Rule

Using _ for an error is legal:

value, _ := something()

but don’t blindly ignore errors.


7. _ with Map Lookup

Maps have a very useful two-value lookup syntax.

Consider:

ages := map[string]int{
    "Rajesh": 42,
    "John":   35,
}

You can do:

age, exists := ages["Rajesh"]

Example:

package main

import "fmt"

func main() {
    ages := map[string]int{
        "Rajesh": 42,
        "John":   35,
    }

    age, exists := ages["Rajesh"]

    fmt.Println(age)
    fmt.Println(exists)
}

Output:

42
true

Suppose you don’t care about the age.

You only want to know:

Does "Rajesh" exist in the map?

Use:

_, exists := ages["Rajesh"]

Example:

package main

import "fmt"

func main() {
    ages := map[string]int{
        "Rajesh": 42,
    }

    _, exists := ages["Rajesh"]

    if exists {
        fmt.Println("Rajesh exists")
    }
}

Very common Go pattern:

if _, exists := ages["Rajesh"]; exists {
    fmt.Println("Found")
}

Think of it as:

ages["Rajesh"]
      │
      ├── value → _
      │           ignored
      │
      └── exists → true/false

8. _ with Type Assertions

Go type assertions can also return two values:

value, ok := something.(string)

For example:

package main

import "fmt"

func main() {
    var data any = "Hello"

    value, ok := data.(string)

    fmt.Println(value)
    fmt.Println(ok)
}

Output:

Hello
true

Suppose you only want to know whether data contains a string.

Use:

_, ok := data.(string)

Example:

package main

import "fmt"

func main() {
    var data any = "Hello"

    _, ok := data.(string)

    if ok {
        fmt.Println("data contains a string")
    }
}

9. _ with Multiple Assignments

The blank identifier can appear anywhere in an assignment.

Example function:

func values() (int, int, int) {
    return 10, 20, 30
}

You can ignore the middle value:

a, _, c := values()

Full example:

package main

import "fmt"

func values() (int, int, int) {
    return 10, 20, 30
}

func main() {
    a, _, c := values()

    fmt.Println(a)
    fmt.Println(c)
}

Output:

10
30

Visual:

values()

10    20    30
↓      ↓     ↓
a      _     c
       │
     ignored

You can ignore several:

_, _, c := values()

or:

a, _, _ := values()

10. _ with Function Parameters

You can also use _ for a function parameter that you intentionally don’t need.

For example:

func calculate(_, b int) int {
    return b * 2
}

Full example:

package main

import "fmt"

func calculate(_, b int) int {
    return b * 2
}

func main() {
    result := calculate(10, 20)

    fmt.Println(result)
}

Output:

40

The first argument:

10

was received but intentionally ignored.

This is less common in beginner programs but can be useful with callbacks or interfaces where a function signature requires parameters you don’t need.


11. _ with range over Maps

Suppose:

prices := map[string]float64{
    "Small":  2.50,
    "Medium": 3.50,
    "Large":  4.50,
}

A map range provides:

for key, value := range prices {

Example:

for size, cost := range prices {
    fmt.Println(size, cost)
}

You might get:

Small 2.5
Medium 3.5
Large 4.5

If you only care about the price:

for _, cost := range prices {
    fmt.Println(cost)
}

Here:

key   → _
value → cost

12. Your Original Menu Example

Your screenshot contained something like:

func printMenu() {
    for _, item := range menu {
        fmt.Println(item.name)

        for size, cost := range item.prices {
            fmt.Printf("\t%10s%10.2f\n", size, cost)
        }
    }
}

Let’s break it down.

The outer loop:

for _, item := range menu {

means:

menu
 │
 ├── index → _       ignored
 │
 └── value → item    used

The programmer doesn’t need:

0
1
2
3

They only need each menu item.

The inner loop:

for size, cost := range item.prices {

uses both returned values:

size → Small
cost → 2.50

So _ is unnecessary there.


13. Blank Imports: import _ "package"

Now we get into an important advanced use.

Sometimes you will see:

import _ "some/package"

For example:

import _ "github.com/lib/pq"

This is called a blank import.

Normally Go does not allow you to import a package without using it.

This produces an error:

import "github.com/lib/pq"

if you never reference pq.

But sometimes you don’t need to call functions from that package.

You only want Go to execute the package’s:

func init()

logic.

Then you import it using:

import _ "github.com/lib/pq"

Meaning:

Import this package for its side effects, but don’t give me a package identifier to use directly.

This has historically been common with things like database drivers, image formats, and plugins.

Conceptually:

import _ "some/package"
          │
          ↓
      load package
          │
          ↓
      run init()
          │
          ↓
don't reference package directly

14. _ for Interface Implementation Checks

This is a very useful advanced Go pattern.

Suppose you have an interface:

type Speaker interface {
    Speak()
}

And a struct:

type Person struct{}

With:

func (p Person) Speak() {
    fmt.Println("Hello")
}

Developers sometimes write:

var _ Speaker = Person{}

Full example:

package main

import "fmt"

type Speaker interface {
    Speak()
}

type Person struct{}

func (p Person) Speak() {
    fmt.Println("Hello")
}

var _ Speaker = Person{}

func main() {
    p := Person{}
    p.Speak()
}

What does this do?

It tells the Go compiler:

Make sure Person implements Speaker.

But we don’t actually need to create a useful variable.

So the result is assigned to:

_

If Person stops implementing Speaker, the program fails to compile.

You will often see:

var _ MyInterface = (*MyType)(nil)

This is called a compile-time interface assertion.


15. _ = variable

You may occasionally see:

_ = value

For example:

func main() {
    name := "Rajesh"

    _ = name
}

This compiles.

Without:

_ = name

Go would complain:

declared and not used: name

So this:

_ = name

basically tells Go:

Yes, I know name exists. I am intentionally not using it.

This can occasionally be useful while experimenting or debugging.

But don’t make this a habit.

Instead of:

name := getName()
_ = name

usually remove the unnecessary variable if you don’t need it.


16. _ Is NOT a Normal Variable

This point is extremely important.

You can assign:

_ = 100

You can assign again:

_ = "Hello"

And again:

_ = true

But you cannot do:

fmt.Println(_)

This is invalid.

Why?

Because _ doesn’t store a value that you can retrieve.

Think about _ as a trash bin:

100 ────────┐
            │
"Hello" ────┼──→  _
            │     🗑️
true ───────┘

Nothing comes back out.

So:

_ = 100

means:

throw away 100.

It does NOT mean:

create a variable named _ containing 100.


17. _ Can Be Used Multiple Times

Unlike normal variable names, _ can appear many times:

_, _, result := values()

For example:

func data() (string, int, bool) {
    return "Rajesh", 42, true
}

func main() {
    _, _, active := data()

    fmt.Println(active)
}

Output:

true

Both unwanted values disappear.


18. Short Variable Declaration and _

Consider:

value, err := getData()

:= creates variables.

But _ does not count as a newly declared variable.

For example:

_, err := getData()

creates only:

err

It does not create _.

This becomes important when learning Go’s := rules later.


19. Common Patterns You Will See in Real Go Code

Pattern 1 — Ignore slice index

for _, user := range users {
    fmt.Println(user)
}

Very common.


Pattern 2 — Map existence check

if _, ok := users["Rajesh"]; ok {
    fmt.Println("User exists")
}

Very common.


Pattern 3 — Ignore unwanted return value

result, _ := calculate()

Common, but be careful if the ignored value is an error.


Pattern 4 — Check type only

if _, ok := data.(string); ok {
    fmt.Println("It is a string")
}

Common.


Pattern 5 — Side-effect import

import _ "some/package"

More advanced.


Pattern 6 — Interface compile-time check

var _ SomeInterface = (*SomeType)(nil)

Very common in professional Go libraries.


20. When Should You NOT Use _?

The most important case is errors.

Avoid this unless you’re certain the error does not matter:

data, _ := os.ReadFile("config.txt")

If the file doesn’t exist, you’ve thrown away the explanation.

Better:

data, err := os.ReadFile("config.txt")

if err != nil {
    fmt.Println("Unable to read file:", err)
    return
}

A good rule:

_ should mean “I intentionally don’t need this value,” not “I don’t want to deal with this value.”


21. Quick Reference Table

SituationExampleMeaning
Ignore range indexfor _, v := range xIgnore index
Ignore function resultx, _ := f()Ignore second return
Ignore map value_, ok := m[k]Only check key existence
Ignore type assertion value_, ok := x.(T)Only check type
Ignore parameterfunc f(_ int)Parameter intentionally unused
Mark value unused_ = xExplicitly discard value
Side-effect importimport _ "pkg"Run package initialization only
Interface checkvar _ I = T{}Verify interface implementation

22. Beginner Mental Model

Whenever you see:

_

mentally replace it with:

IGNORE THIS VALUE

So:

for _, item := range menu {

becomes:

for IGNORE_INDEX, item := range menu

And:

value, _ := function()

becomes:

value, IGNORE_SECOND_RESULT := function()

And:

_, ok := myMap["Rajesh"]

becomes:

IGNORE_VALUE, check_if_it_exists

That mental model works for almost every beginner use of _.


23. Mini Practice Program

Try this complete program:

package main

import "fmt"

func getUser() (string, int, string) {
    return "Rajesh", 42, "India"
}

func main() {

    // 1. Ignore slice index
    languages := []string{"Go", "Python", "Java"}

    for _, language := range languages {
        fmt.Println(language)
    }

    // 2. Ignore second function result
    name, _, country := getUser()

    fmt.Println(name)
    fmt.Println(country)

    // 3. Ignore map value
    users := map[string]int{
        "Rajesh": 42,
        "John":   35,
    }

    _, exists := users["Rajesh"]

    fmt.Println("Exists:", exists)
}

Output:

Go
Python
Java
Rajesh
India
Exists: true

This one small program demonstrates three of the most important uses of the blank identifier.


24. Final Rule to Remember

The entire concept can be summarized in one sentence:

_ = "I intentionally don't need this value."

For your current learning stage, master these three uses first:

// 1. range
for _, item := range items {
}

// 2. multiple return values
value, _ := function()

// 3. map existence
_, ok := myMap["key"]

Once these feel natural, the advanced uses such as:

import _ "package"

and:

var _ Interface = (*Type)(nil)

will be much easier to understand.

Related Posts

Go Tutorials: Go Methods

Go methods become much easier once you understand one idea: A method is simply a function attached to a type. And the important rule is: A receiver…

Read More

Go Tutorials: Modules, Packages, Subpackages, Submodules, and Workspaces

This tutorial gives you one complete mental model for: The most important idea is: And sometimes: These two designs are very different. 1. What is a Go…

Read More

Go Tutorials: Numeric Types Beginner Tutorial

Go has four main families of numeric types: The simplest way to remember them is: Type family Stores Example int Whole numbers, positive or negative -10, 0,…

Read More

Go Tutorials: Go Testing, Benchmarking, and Profiling

These three topics become much easier once you separate the questions they answer: Topic Main question Main Go tool Result Testing Does my code work correctly? go…

Read More

Go Tutorials: Generics

1. What are Generics? Generics let you write one piece of code that works with multiple types while keeping Go’s compile-time type safety. Suppose you want a…

Read More

Go Concurrency Management Made Simple

This tutorial covers: The goal is: One concept → one purpose → one complete runnable example. Every example is independent. Save any example as: and run: 0….

Read More