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:
- use the value, or
- 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
PersonimplementsSpeaker.
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
nameexists. 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
_containing100.
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
| Situation | Example | Meaning |
|---|---|---|
| Ignore range index | for _, v := range x | Ignore index |
| Ignore function result | x, _ := 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 parameter | func f(_ int) | Parameter intentionally unused |
| Mark value unused | _ = x | Explicitly discard value |
| Side-effect import | import _ "pkg" | Run package initialization only |
| Interface check | var _ 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.