Last Updated: May 22, 2026
A variable is a named place in memory that holds a value of a specific type. Go gives you several ways to declare one, and the var keyword is the foundation that all the other forms build on. This lesson covers var in full, including type inference, multiple declarations, grouped blocks, scope, and the rules Go enforces around unused variables.
var name type = valueThe longest and most explicit way to declare a variable in Go uses three pieces: the var keyword, the variable name, and the type, followed by an initial value.
Read each declaration left to right: "declare a variable called productName, of type string, with the value Wireless Headphones." Go is strict about types, so you can't later assign a number to productName or a string to price. If you try, the compiler stops you.
This strictness is one of the reasons Go programs tend to be reliable. A variable's type is set at declaration and never changes.
var name = valueWriting the type every time gets repetitive, especially when the value already makes the type obvious. Go can infer the type from the initial value, so you can drop the type annotation.
The compiler picks the type from the value on the right. A double-quoted string gives you a string. A number with a decimal point gives you a float64. A whole number gives you an int. The keyword true or false gives you a bool.
When Go infers a type from a plain number literal, it picks int for whole numbers and float64 for decimals. So var quantity = 5 gives you an int, not an int32 or int64. If you specifically need a different size, you have to say so:
For now, just know that inference defaults to int and float64.
var name typeYou can declare a variable without giving it an initial value. Go fills in a default called the zero value.
Every type in Go has a zero value. For numbers it's 0, for strings it's "", for booleans it's false. A var declaration without a value is always safe to use, because the variable always has a defined value of its declared type.
This is different from many other languages where reading an uninitialized variable gives you garbage memory or a runtime error. In Go, that situation cannot occur.
The diagram above shows the rule applied to three different types. The var declaration on the left produces the zero value on the right. There is no "uninitialized" state in between.
When you do this form (var name type with no value), you have to provide the type, because there is no value for Go to infer from.
When you have several variables of the same type, you can declare them in a single var statement.
All three variables get the same type, string, and all three get the zero value for strings (empty string) until assigned. The same pattern works with initial values too:
When you provide values, Go infers a type for each variable from its corresponding value. The three variables don't have to end up the same type. This works fine:
name is a string, price is a float64, and inStock is a bool. Go figures it out from the values.
var ( ... )When you have a bunch of related variables, declaring them one per line gets noisy. Go gives you a grouped form using parentheses, similar to imports.
The grouped form is purely cosmetic. The compiler treats it the same as four separate var lines. What it gives you is readability: aligned types, aligned values, and an obvious visual block for "here is a group of related variables."
Grouped var blocks appear most often at the package level, where they declare values that belong to the whole package rather than a single function. The next section covers that distinction.
Where you declare a variable changes who can see it and how long it lives.
A variable declared inside a function is called a local variable. It exists only while that function is running, and only the function can see it.
A variable declared outside any function, at the top of a file, is called a package-level variable. Every function in the package can read and write it.
storeName and taxRate are declared at the package level. Both printGreeting and printTax can read them without being passed those values as arguments. The local variable customer lives only inside main. If printGreeting tried to use customer directly, the compiler would say undefined: customer.
Cost: Package-level variables are initialized once when the program starts, before main runs. Putting expensive work in their initializers (such as reading files, opening database connections, or building large maps) makes program startup slower for every run, even when that data isn't needed. Prefer lazy initialization inside functions when the work is heavy or conditional.
Package-level variables look convenient, but they come with a real cost: any function can change them, so it becomes hard to track where the value comes from when something goes wrong. Most Go programs use them sparingly, reserving them for things that genuinely apply to the whole package (configuration loaded once at startup, a shared logger, version strings). For everything else, local variables passed as arguments are clearer.
There is one more rule to know. Package-level variables must use var (or const). The short declaration form := only works inside functions.
Go does not let you declare a local variable and never use it. The compiler treats unused locals as an error, not a warning.
This produces:
You have two ways to fix it: either use the variable, or remove it. Most of the time the right answer is to remove it. If something exists in your code, it should pull its weight.
There is an interesting wrinkle here. The unused-variable rule only applies to local variables. Package-level variables can be declared and never used, and Go won't complain.
This compiles. The reason is practical: package-level variables are often part of a package's exported surface, used by other files or other packages. Forcing every package-level variable to be used in the same file would be too aggressive.
The same rule applies to unused imports: the compiler rejects them. This pair of rules (no unused locals, no unused imports) keeps Go files tight. Dead code doesn't accumulate the way it does in languages that only emit warnings.
When you declare a variable inside a block, it can hide an outer variable with the same name. This is called shadowing.
The inner var price = 50.00 creates a brand-new variable that exists only inside the if block. While that block is running, references to price see the inner value. Once the block ends, the inner price disappears and the outer one is visible again.
Shadowing is sometimes useful (a short-lived alternative value inside a specific block), but it can also hide bugs. If you meant to update the outer variable and accidentally declared a new one, the compiler can't tell. The most common place shadowing causes bugs is with :=, the short declaration operator.
For now, just remember: a var declaration always creates a new variable in the current scope, and if a variable with the same name already exists in an outer scope, the inner one shadows it for the duration of that block.
Here is one program that uses every form covered in this lesson. It prints a small order summary for a customer at an online store.
Look at the variety:
var block at the package level for storeName and taxRate.customerName.orderID.subtotal, which gets assigned later.tax and total use :=, which is what the next lesson is about.Every var form has its place. The fully explicit form is useful when the type isn't obvious from the value. Inferred types are great when the value tells the whole story. Zero-value declarations are right when you'll assign later in a loop or a conditional. Multi-variable and grouped forms keep related declarations together.