A variable is a named container that holds a value. Every variable in Go has a type — a label that tells Go what kind of value it can hold (a number, a piece of text, a true/false flag, etc.).
Go is a statically typed language. That means once a variable’s type is decided, it can never hold a value of a different type. This sounds restrictive, but it’s the language’s superpower: most of the bugs in dynamic languages get caught at compile time in Go, before your program ever runs.
Declaring variables
There are three ways to create a variable in Go. Here they all are in one runnable file:
package main
import "fmt"
func main() {
// explicit type and value
var name string = "Manikandan"
// type inferred from the right-hand side
var age = 30
// shorthand declaration (only inside functions)
city := "Chennai"
fmt.Println(name, age, city)
}
Run it:
go run main.go
Manikandan 30 Chennai
Each style does the same thing — creates a variable and gives it a value. The differences:
var name string = "Manikandan"— the most explicit form. You writevar, the variable name, the type, then the value. Verbose but unambiguous.var age = 30— same as above but Go figures out the type from the value.30is a whole number, so Go infersint.city := "Chennai"— the short declaration. Only valid inside functions. This is the form you’ll write 90% of the time.
All three produce identical results. Use
:=inside functions,varfor package-level variables (which we’ll see later).
Declaring multiple variables at once
Go lets you declare several variables on a single line. This is handy when you want to group related values together:
// inside main()
a, b, c := 10, 20, 30
fmt.Println(a, b, c)
10 20 30
The types don’t need to match — Go infers each one independently:
// inside main()
name, age, active := "Manikandan", 30, true
fmt.Println(name, age, active)
You can also do this with var, and even group declarations in a block:
// inside main()
var x, y int = 1, 2
var (
host = "localhost"
port = 8080
enabled = true
)
fmt.Println(x, y, host, port, enabled)
The var (...) block is the standard way to declare several package-level variables together — you’ll see it in real Go projects all the time.
Zero values
If you declare a variable without assigning it a value, Go fills it with a sensible default called the zero value:
// inside main()
var count int
var name string
var ready bool
fmt.Println(count, name, ready)
0 false
intzero value is0stringzero value is""(an empty string — that’s whynameprinted as nothing)boolzero value isfalse
This is a deliberate Go design choice. There are no null, nil, or undefined surprises for basic types — every variable always has a real value.
The basic types
Go has a small, focused set of built-in types. Here’s the full list:
| Category | Type | Description | Zero value |
|---|---|---|---|
| Boolean | bool | true or false | false |
| String | string | Immutable sequence of bytes (UTF-8 text) | "" |
| Signed integer | int | Platform-sized integer (32- or 64-bit) | 0 |
int8, int16, int32, int64 | Fixed-size signed integers | 0 | |
| Unsigned integer | uint | Platform-sized unsigned integer | 0 |
uint8, uint16, uint32, uint64 | Fixed-size unsigned integers | 0 | |
uintptr | Unsigned integer big enough to hold a pointer | 0 | |
| Floating point | float32, float64 | IEEE-754 floating-point numbers | 0 |
| Complex | complex64, complex128 | Complex numbers (real + imaginary parts) | 0+0i |
| Aliases | byte | Alias for uint8 — used for raw binary data | 0 |
rune | Alias for int32 — represents a Unicode code point | 0 |
In day-to-day code you’ll mostly reach for int, float64, string, and bool. The sized variants exist for when you need to control memory layout exactly (file formats, network protocols, embedded systems).
A quick taste of each:
// inside main()
var count int = 42
var pi float64 = 3.14159
var greeting string = "Hello"
var ready bool = true
fmt.Println(count, pi, greeting, ready)
Strings deserve one extra note. They’re wrapped in double quotes "..." and are immutable — once created, a string can never be changed. For multi-line strings or text with special characters, use backticks:
// inside main()
poem := `Roses are red
Violets are blue
Go is a language
And so are you`
fmt.Println(poem)
Go has three different quote marks — double quotes, single quotes, and backticks — and each one means something different. The String Quotes Cheatsheet explains when to use which.
Numeric literals can use underscores for readability:
1_000_000is the same as1000000. Go ignores them.
Printing values
You’ve been using fmt.Println to print everything so far. When you need more control — a specific number of decimals, hex output, struct field names — switch to fmt.Printf and use a format verb for each value:
// inside main()
count := 42
pi := 3.14159
greeting := "Hello"
ready := true
fmt.Printf("%d %f %s %t\n", count, pi, greeting, ready)
fmt.Printf("%v %T\n", count, count) // value, then type
42 3.141590 Hello true
42 int
The five verbs you’ll reach for most:
| Verb | Use it for |
|---|---|
%v | Any value, default format — when in doubt, use this |
%d | Integer (base 10) |
%s | String |
%f | Floating-point number |
%T | The type of the value (great for debugging) |
There’s a whole family of verbs for hex, binary, width, precision, and struct formatting — the fmt Printing Cheatsheet at the end of the course covers them in full.
Type conversions
Go does not convert between types automatically. If you try to add an int to a float64, the compiler rejects it. You have to convert explicitly:
// inside main()
i := 10
f := 3.5
// total := i + f // ❌ compile error
total := float64(i) + f // ✅ explicit conversion
fmt.Println(total)
13.5
This feels annoying at first but saves you from a category of subtle bugs that plague languages like JavaScript and Python. When you write float64(i), you’re telling both the compiler and the next person who reads your code: “yes, I meant to do this.”
Constants
A constant is a value that can never change after it’s declared. Use the keyword const:
package main
import "fmt"
const Pi = 3.14159
const AppName = "GoTutor"
const MaxRetries = 3
func main() {
fmt.Println(AppName, "version", MaxRetries)
fmt.Println("Pi is approximately", Pi)
}
GoTutor version 3
Pi is approximately 3.14159
Constants are perfect for values that should never change during the program’s life — configuration values, mathematical constants, or fixed identifiers.
You can group related constants in a const block:
const (
StatusActive = "active"
StatusInactive = "inactive"
StatusBanned = "banned"
)
The compiler will refuse to compile code that tries to reassign a constant — a guarantee no comment or convention can ever match.
Type definitions
Sometimes the built-in types aren’t descriptive enough. Go lets you create your own type that’s based on an existing one:
package main
import "fmt"
type UserID int
type Email string
func main() {
var id UserID = 42
var addr Email = "[email protected]"
fmt.Println(id, addr)
}
UserID is a brand-new type whose underlying type is int. They behave the same — you can do math on a UserID — but Go treats them as different types in your code:
// inside main()
var id UserID = 42
var n int = 10
// total := id + n // ❌ compile error
total := int(id) + n // ✅ explicit conversion
fmt.Println(total)
This is hugely useful in real code. If a function expects a UserID, you can never accidentally pass it a random int. The compiler catches the mistake immediately.
Naming conventions
Go has a strict, simple naming convention:
camelCasefor variables and functions used inside one file/package:userName,currentAgePascalCasefor variables and functions visible to other packages:UserName,CurrentAge
The case of the first letter isn’t just style — it’s how Go decides what’s exported (visible outside the package) and what’s not. We’ll come back to this in the Packages section.
What’s next
You now know how to store data in Go. In the next lesson, we’ll do something with that data — comparing values, doing math, and making decisions with operators and conditionals.