How to Declare Multiple Variables in Go

Declare multiple Go variables on one line using comma-separated names or a block declaration for mixed types.

Story / scenario opener

You're writing a data processing function. You need a counter for lines read, a buffer for the current batch, and a flag to track if the stream ended. In Python or JavaScript, you'd probably just type count = 0, buffer = [], and done = false on separate lines. Go gives you tools to group these declarations, but the syntax changes depending on whether you're at the package level, inside a function, or initializing values right away. The compiler is strict about scope and initialization, so picking the wrong form can lead to cryptic errors or messy code.

Concept in plain words

Go treats variable declaration as a deliberate act. You can declare variables using the var keyword, which allows you to reserve names and let the language assign default zero values. You can also use the short declaration operator :=, which infers the type from the value and initializes the variable in one step. When you have multiple variables, Go lets you group them. Grouping signals to the reader that these variables belong together logically, even if their types differ. The compiler enforces that every declared variable is used, and it tracks scope strictly.

Go's zero values are a design feature. In many languages, an uninitialized variable holds garbage data. In Go, every variable gets a sensible default the moment you declare it. This safety applies to multiple declarations too. When you declare a batch of variables, you get a batch of safe defaults. You can use them immediately without initialization. The language guarantees this behavior.

Minimal example

Here's the simplest way to declare multiple variables of the same type without initializing them.

package main

import "fmt"

func main() {
	// Same type, comma-separated names.
	// Zero values assigned automatically.
	var count, total int

	fmt.Println(count, total)
}
# output:
0 0

The variables count and total are both integers. They share the type int. The compiler allocates space for both and sets them to zero. The fmt.Println call prints the values. You can read or modify either variable independently. Grouping them on one line saves vertical space and shows they are related.

Zero values are safe defaults. Trust them.

Zero values and safety

The zero value depends on the type. Integers become zero. Strings become empty strings. Booleans become false. Pointers, slices, maps, and channels become nil. The nil value for slices and maps is safe to use in many contexts. You can append to a nil slice, and it grows automatically. You can read from a nil map, and you get the zero value for the map's value type. This means you often don't need to initialize slices or maps explicitly when you declare them. The zero value is ready to work.

package main

import "fmt"

func main() {
	// Slice starts as nil.
	// Append works on nil slices.
	var items []string

	items = append(items, "first")
	items = append(items, "second")

	fmt.Println(items)
}
# output:
[first second]

The items variable is a slice. It starts as nil. The append function handles nil slices correctly. It allocates a new backing array and adds the element. You don't need to check for nil before appending. This pattern reduces boilerplate. It works for multiple variables too. If you declare var a, b []int, both are nil and both accept appends.

Block declarations for mixed types

When variables have different types or you want to align them for readability, use a block declaration.

package main

import "fmt"

func main() {
	// Block declaration groups related variables.
	// Mixed types allowed.
	// gofmt aligns the types for readability.
	var (
		host   string
		port   int
		secure bool
	)

	host = "localhost"
	port = 8080
	secure = true

	fmt.Printf("Connecting to %s:%d (secure: %v)\n", host, port, secure)
}

The var block is a staple in Go codebases. You'll see it at the top of files to group configuration, or inside functions to set up a batch of state. The parentheses create a scope for the declaration, but the variables are still accessible in the enclosing scope. The real benefit is visual. gofmt aligns the types in a var block, creating a clean column that lets you scan variable names and types at a glance. This alignment is a community convention. Run gofmt on your code, and it will adjust the spacing automatically. Trust the tool. Argue logic, not formatting.

You can also initialize variables inside a block. The syntax supports both explicit types and type inference.

package main

import "fmt"

func main() {
	// Initialization in a block.
	// Explicit types on the left.
	var (
		name string = "Alice"
		age  int    = 30
	)

	// Type inference in a block.
	// Omit types, compiler infers from values.
	var (
		city    = "Wonderland"
		version = 1.5
	)

	fmt.Printf("%s is %d years old.\n", name, age)
	fmt.Printf("Version %v in %s.\n", version, city)
}

The first block uses explicit types. The second block omits types. The compiler infers string for city and float64 for version. Both forms are valid. Use explicit types when the type isn't obvious from the value. Use inference when the type is clear.

Short declarations inside functions

Inside functions, you often want to declare and initialize at the same time.

package main

import "fmt"

func main() {
	// Short declaration infers types from values.
	// All variables on the left must be new or one must be new.
	x, y, z := 1, 2, 3

	fmt.Println(x, y, z)
}

The := operator is the workhorse for local variables. It declares variables and assigns values simultaneously. The compiler infers the types from the right-hand side. You can declare multiple variables in one line. The values map positionally to the names. This form is concise and keeps the declaration close to the first use. However, := has a strict rule: at least one variable on the left must be new in the current scope. If you try to redeclare an existing variable without introducing a new one, the compiler rejects the code.

This rule enables a common pattern for error handling. You can update an existing error variable while declaring a new result variable.

package main

import "fmt"

func process() (string, error) {
	return "result", nil
}

func main() {
	// Declare err and result.
	result, err := process()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Update err, declare newResult.
	// At least one variable must be new.
	newResult, err := process()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(result, newResult)
}

The second call uses :=. It updates err and declares newResult. The compiler sees newResult is new, so it allows the statement. This pattern avoids declaring err separately before every call. It keeps the code tight.

Group variables that share a lifecycle.

Pitfalls and compiler errors

The compiler catches common mistakes early. If you try to declare variables of different types using the comma-separated syntax, the build fails. You cannot write var x, y int, string. The compiler expects a single type after the names. It rejects this with a syntax error. If you need mixed types, switch to a block declaration or separate lines.

Redeclaration errors happen when you use := and all variables already exist. If you write x := 1 and then x := 2 in the same scope, the compiler stops with no new variables on left side of :=. This happens often when refactoring or copying code. The fix is to use = for assignment if all variables are already declared, or remove the duplicate declaration.

Unused variables trigger a build failure. If you declare var x, y int and only use x, the compiler rejects the program with y declared and not used. This rule prevents stale variables from cluttering the code. Remove unused variables or use the blank identifier _ if you need to discard a value. The blank identifier discards a value intentionally. Use it sparingly with errors. Dropping an error silently can hide bugs.

The worst goroutine bug is the one that never logs. The same applies to errors. Declare only what you need.

Decision: when to use this vs alternatives

Use var x, y Type when you need multiple variables of the same type and want to rely on zero values.

Use a var block with parentheses when you have variables of different types that belong together logically, or when you want gofmt to align the types for readability.

Use x, y := value1, value2 when you are inside a function and want to declare and initialize variables in one step with inferred types.

Use separate declarations when the variables serve unrelated purposes; grouping unrelated variables creates noise for the reader.

Where to go next