1. What is a variable in Go: name, type, and value
When you’re just starting to program, it’s easy to think of a variable as a “little box for a number”. That’s not a bad start, but in Go this “box” has a passport: a name, a type, and the current value. And the most important thing: the type is fixed at declaration and doesn’t “change shoes” on the fly. This disciplines the code and makes it more predictable (and yes, sometimes it’s annoying—but more often it saves you from weird bugs).
Imagine a variable as a conference badge: the badge shows the name (how to address it), the “role” (type), and the current “state” (value). If the badge says int, you can’t suddenly write a string "hello" on it—compiler security won’t let it through.
Declaration vs assignment: why these are different operations
In practice, beginners most often get confused not by syntax, but by meaning: what’s the difference between “create a variable” and “change the value in an already created variable”. In Go, these actions are separated very clearly. It’s like the difference between “create a new character in a game” and “change their outfit”: the character’s name appears when you create them, and after that you just update their parameters.
In Go, there are three forms we’ll use every day:
- var — variable declaration (create a name in the current scope).
- := — short variable declaration (also creates a name, usually inside functions).
- = — assignment (the name must already exist; we change the value).
Let’s put it into a table—this makes it easier for your brain to “grab onto”:
| Syntax | What it does | When to use it |
|---|---|---|
|
declares x of type int, gives a default value | when you want to explicitly specify the type or declare ahead of time |
|
declares x; the type is inferred from 10 | when you have a starting value and the type is obvious |
|
declares x (type inferred), short form | usually inside main and other functions |
|
assigns 10 to an already existing x | when x is already declared |
And here’s the key phrase of the day: var and := create a variable, while = only changes its value.
2. Declaring with var: two variants
var looks a bit more “official” than :=. Sometimes that’s even nice: the code feels more serious, as if you put a jacket on the variable. But the main thing is that var shows the type well and is useful when you want to declare a variable ahead of time and fill it later (for example, after fmt.Scan or after checking a condition).
var name Type: declared, default value
When you write var count int, you create a variable count of type int. If you don’t give it an initial value, Go still won’t leave it “with garbage”—it will set the default value (for int that’s 0). We’ll break down default values in detail separately, but it’s already important to know: the program won’t read “random stuff from memory”.
A minimal example to feel the difference between declaration and assignment:
package main
import "fmt"
func main() {
var count int // declared
count = 3 // assigned
fmt.Println(count) // 3
}
Here var count int creates a variable, and the line count = 3 only changes its value.
var name = expr: declared and initialized immediately
If you write var x = 10, Go looks at the right-hand side and infers the type automatically. This is called type inference. For example, if there’s a string on the right, the type will be string. In official Go materials, this is often explained with a very clear example: var str = "Hello, 世界" means the same as var str string = "Hello, 世界".
Here’s a small example:
package main
import "fmt"
func main() {
var name = "Gopher"
var age = 10
fmt.Println(name, age) // Gopher 10
}
Here name becomes a string, and age becomes an int, because Go inferred the type from the right-hand side.
4. := and = in everyday code
Short declaration := inside a function
:= is a “quick declaration”. It’s used almost all the time inside main, because that’s where we write the main flow of the program, and we want fewer ceremonies. In practice, := makes code compact and readable: you immediately see the name and the initial value. It’s like saying, “here’s a variable, and here’s its start”.
Example:
package main
import "fmt"
func main() {
x := 10 // declared + initialized
x = x + 5 // updated an existing one
fmt.Println(x) // 15
}
Pay attention to the rhythm: the first line creates the variable, then we use = to update it. This is a very typical Go coding style.
There’s an important rule to memorize for now like a mantra: if the variable already exists and you want to change it—use =. If you write :=, you are most likely creating a new variable (and that is sometimes not what you intended).
Assignment =: changing the value of an existing variable
The = operator does not create a variable. It requires that the name has already been declared in the current scope (or in an outer scope that is visible). So x = 10 without a prior declaration of x will lead to a compilation error—and that’s normal: the compiler honestly says, “I don’t know what x is.”
An example of the “correct” sequence:
package main
import "fmt"
func main() {
var total int
total = 100
total = total - 30
fmt.Println(total) // 70
}
Here total is created via var, and then updated twice using =.
By the way, due to Go’s strict typing, it doesn’t like to automatically “mix” different numeric types: if variables are of different types, the compiler will ask you to be more explicit. This is part of Go’s philosophy: it’s better to forbid ambiguity upfront than to catch weird effects later.
5. Block scope {}: where a variable is “visible”
Now the most interesting part (and the most common “why doesn’t it work?!” among beginners). In Go, curly braces {} define scope: the part of code where variable names exist and are accessible. You can think of a block as a room: you can bring things (variables) into it, use them inside, but when you leave the room—things created inside stay there.
Schematically it looks like this:
main {
x // lives throughout main
if {
y // lives only inside if
}
// here y no longer exists
}
This isn’t “Go being picky”, it’s protection against chaos: if a variable is needed only inside a small block, it’s better not to let it live longer—there’s less chance you’ll accidentally use it where you shouldn’t.
Nested if blocks
The simplest example: a variable declared inside if is not accessible outside.
package main
import "fmt"
func main() {
x := 10
if x > 0 {
y := x * 2
fmt.Println("inside:", y) // inside: 20
}
fmt.Println("outside:", x) // outside: 10
}
Here y exists only inside the if block. That’s convenient: y is a “temporary calculation”, it’s not needed later.
If you try to write fmt.Println(y) after the if, the compiler will say that y is undefined. And that’s a good moment: the compiler literally saves you from using a variable that has already “died”.
Loop variables in for
With the for loop, it’s a similar story: everything declared in the loop header usually lives inside the loop.
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
fmt.Println(i) // 1, then 2, then 3
}
}
The variable i is created within the loop. After the loop finishes, it’s no longer needed, and Go doesn’t let it “hang around” in main. This makes code cleaner: fewer “extra” names that you might accidentally use later.
When to declare a variable outside vs inside
There’s a very practical logic here. If you need the value after the block, declare the variable outside and do an assignment inside the block. If you need the value only for an internal computation, declare it right inside the block so it doesn’t interfere with the rest of the program.
Let’s see the difference in one example: suppose you want to calculate a discount, but also use it after the if.
package main
import "fmt"
func main() {
total := 120
discount := 0 // declared ahead of time
if total >= 100 {
discount = 10 // assignment, NOT declaration
}
fmt.Println("discount:", discount) // discount: 10
}
Here discount is declared before the if, because it’s important for us to print it after the condition. Inside the if we don’t create a new variable; we update the existing one.
6. Mini app “BudgetBuddy”: variables and scope
Let’s write a small console app. It will be simple, but useful: we’ll track a budget and expenses. We won’t use functions, collections, or complex structures—only what we already know: input, variables, if, for, and output. It’s like a mini cash register or an expense tracker at the “mom, I’m a programmer” level.
Version 1: read the budget and one purchase
First, we’ll make a very simple version: the user enters a budget and one purchase, and the program prints how much is left.
package main
import "fmt"
func main() {
var budget int
var purchase int
fmt.Scan(&budget, &purchase)
rest := budget - purchase
fmt.Println(rest) // for example: 700
}
Notice the style. We read values into variables, then compute rest using :=, because it’s an intermediate result and its type is obvious.
Version 2: a loop over N purchases and why spent must be outside
Now let’s do something more useful: let the user enter the number of purchases n, and then enter n amounts. We need an accumulator spent that lives outside the loop; otherwise, after the loop we won’t be able to use it.
package main
import "fmt"
func main() {
var budget, n int
fmt.Scan(&budget, &n)
spent := 0
for i := 0; i < n; i++ {
var x int
fmt.Scan(&x)
spent = spent + x
}
fmt.Println(budget - spent)
}
Here spent is declared outside the loop because it’s needed after the loop. But x is an internal variable for one iteration: read one purchase, add it to the sum, forget it. This is a good example of “narrow” scope: fewer variables live long—less confusion.
Version 3: budget check with if
Let’s add a condition: if we spent more than the budget, print a warning. Notice that spent lives throughout main, which means it’s available after for and inside if.
package main
import "fmt"
func main() {
var budget, n int
fmt.Scan(&budget, &n)
spent := 0
for i := 0; i < n; i++ {
var x int
fmt.Scan(&x)
spent = spent + x
}
if spent > budget {
fmt.Println("over budget")
}
fmt.Println("spent:", spent)
}
Here we have two important effects. First, spent is declared exactly where it “starts” in meaning—before the loop. Second, the variable x doesn’t spread across the function: it’s needed for 2–3 lines, so it lives for 2–3 lines.
7. Typical mistakes when declaring and using variables
Mistake #1: using = for a name that doesn’t exist yet.
This is one of the most common situations: someone writes sum = 0 because “well, I’m assigning zero”, but they never declared sum anywhere before. In Go, you can’t do that: first declare (var sum int or sum := 0), and only then change (sum = sum + x). A good habit is to train yourself to spot the first encounter with a variable: it must use var or :=.
Mistake #2: expecting a variable from if or for to be available after the block.
Psychologically it’s very understandable: you just computed y, it’s so nice, and you want to use it further. But if y is declared inside {}, then after the closing brace it’s as if it was “deleted”. The solution is simple conceptually: if you need the value after the block, declare the variable before the block and do an assignment inside.
Mistake #3: declaring variables too early and too “wide”.
A beginner sometimes creates ten variables at the beginning of main “just in case”. Then half aren’t used, and the other half are used 40 lines later, and you no longer remember who is who. In Go, it’s much nicer to declare variables closer to where they’re used: this reduces scope and makes the code easier to read (and for your future self, who will look at the code in a week as if it were someone else’s).
Mistake #4: declaring a variable and not using it.
Go is strict about this: if a function has a variable that isn’t used anywhere, the compiler usually complains. At first it’s irritating (“I just wanted it for later!”), but it quickly becomes your ally: it doesn’t let you keep garbage and “forgotten ideas” in the code that confuse the reader. If the variable really isn’t needed—delete it. If it’s needed later—write the code so it actually gets used.
Mistake #5: confusing “declaration” and “update” when reading code.
Sometimes a program “doesn’t work” simply because you think a line creates a new variable, while it only changes the old one (or vice versa). A helpful habit is to read code like a story: “here the variable appeared”, “here its value was changed”, “here we use it”. If you learn to see these three stages, 80% of variable-related problems will disappear without any magic or shamanism.
GO TO FULL VERSION