With your tooling ready, it is time to write some Go. This chapter covers the first thing you reach for in any program: declaring variables and constants. We will look at the two ways to declare a variable, the basic types Go provides, and a few rules that might surprise you. For example, in Go, a local variable you never use will not even compile.
The companion code for this chapter is deliberately small. It is a single main.go file with four short functions, one for each idea. The main function calls them in order, and each prints a labeled block. You can run the whole file and read the output from top to bottom.

By the end of this chapter, you will be comfortable with:
var form and the short := form.const, and iota for small numbered lists.Check out the chapter branch to follow along:
bashgit checkout 06-variables-and-types-finish
Go gives you two ways to introduce a variable. The first spells everything out using the var keyword. The second uses :=, which is shorter and figures out the type for you. Here is the variables demo from :
main.gogo// main.gofunc variablesDemo() {fmt.Println("\n-- Variables --")language := "Go"fmt.Println("language:", language)var temperature float64 = 36fmt.Println("temperature:", temperature)language = "Golang"fmt.Println("language (reassigned):", language)}
language := "Go" is the short form. The := operator declares a new variable and infers its type from the value on the right. Because "Go" is a string, language becomes a string. You do not have to state the type explicitly. This is the form you will write most of the time in real Go code.
var temperature float64 = 36 is the long form with an explicit type. You might wonder why anyone would write the type when := infers it automatically. The answer is in the value itself. If you wrote temperature := 36, Go would infer an int, because a whole number like 36 defaults to an integer. We want a float64 here, so we write the type to override the default. A good rule of thumb is to lean on := for most things, and reach for the explicit var form when the default inferred type is not what you need.
The last two lines show a distinction that often trips up newcomers: := versus =. The := operator declares a brand new variable. The = operator assigns a new value to a variable that already exists. So language = "Golang" updates the language variable we declared earlier. Writing language := "Golang" on that line would trigger a compile error because language is not new. The compiler will complain with "no new variables on left side of :=".
There is one more rule worth knowing. The := operator only works inside a function. At the package level, where a declaration lives outside any function, you must use var or const. We will see a package-level declaration later in this chapter.
In some languages, a variable you declare but never assign holds random memory until you set it. Go does not work that way. Every variable you declare without a value is initialized to its type's zero value. This is a defined default rather than leftover garbage. The zeroValuesDemo function declares four variables and prints them without ever assigning anything:
go// main.gofunc zeroValuesDemo() {fmt.Println("\n-- Zero values --")var count intvar ratio float64var name stringvar active boolfmt.Printf("int: %d\n", count)fmt.Printf("float64: %g\n", ratio)fmt.Printf("string: %q\n", name)fmt.Printf("bool: %t\n", active)}
Running it shows the defaults:
int: 0
float64: 0
string: ""
bool: false
A numeric type starts at 0, a string starts at the empty string "", and a bool starts at false. Pointers and a few other types we will meet later start at nil, which is Go's way of saying "nothing here." The practical result is that a freshly declared var is always safe to read. You will rely on this often. A counter that starts at 0 or a flag that starts at false usually needs no initial assignment at all.
The verbs in those Printf calls (%d, %g, %q, %t) are formatting directives from the fmt package. They stand for an integer, a floating-point number, a quoted string, and a boolean. The %q directive is the reason the empty string prints as a visible "" instead of blank space.
Go has a small, predictable set of basic types. The basicTypesDemo function declares one value of each common type. It prints the value next to its type using the %T verb, which asks fmt to report what type a value actually is:
go// main.gofunc basicTypesDemo() {fmt.Println("\n-- Basic types --")count := 42var big int64 = 9_000_000_000price := 19.99greeting := "héllo"ready := truefmt.Printf("%-9v has type %T\n", count, count)fmt.Printf("%-9v has type %T\n", big, big)fmt.Printf("%-9v has type %T\n", price, price)fmt.Printf("%-9q has type %T\n", greeting, greeting)fmt.Printf("%-9v has type %T\n", ready, ready)fmt.Printf("%q: %d bytes, %d runes\n",greeting, len(greeting), len([]rune(greeting)))}
The output names each inferred type:
42 has type int
9000000000 has type int64
19.99 has type float64
"héllo" has type string
true has type bool
A few details stand out here. count := 42 infers int, the default integer type. This is 64 bits wide on modern machines. When you need to be explicit about width, Go has sized integers like int64. The big variable holds nine billion, a value too large to fit in a standard 32-bit integer. The underscores in 9_000_000_000 are digit separators. Go ignores them; they just make large numbers easier for humans to read.
A decimal number like 19.99 infers float64, Go's default floating-point type. "héllo" is a string, and true is a bool. Notice that four of the five variables used := to let the compiler infer the type. The big variable used the explicit var int64 form for the same reason as the temperature earlier: inference alone would not have given us the exact type we wanted.
The table below summarizes each type with its zero value and the example from the code:

Two more types round out the basics: byte and rune. The next section shows where rune turns up in the demo's last line.
The final Printf prints something that surprises a lot of people:
"héllo": 6 bytes, 5 runes
The word héllo clearly has five characters, so why does it take six bytes? Go strings are UTF-8 encoded. In UTF-8, plain ASCII characters take one byte each, but accented and non-Latin characters take more. The é here takes two bytes, which is why len(greeting) reports 6. Calling len on a string counts bytes, not characters.
To count characters, you convert the string to a slice of runes. A rune is Go's name for a single Unicode code point, which conceptually represents one logical character. It is an alias for int32. Its companion is the byte, an alias for uint8, representing one raw 8-bit byte of data. Converting the string and counting the runes with len([]rune(greeting)) gives 5, the character count we expected.
You do not need to go deep on Unicode to write backend services. The main takeaway is simple: a Go string is a sequence of bytes, the byte length and the character length can differ, and a rune is what you reach for when you mean "one character."
A constant is a value fixed at compile time that can never change while the program runs. You declare one with const instead of var. The constantsDemo function shows a single constant:
go// main.gofunc constantsDemo() {fmt.Println("\n-- Constants --")const pi = 3.14159fmt.Println("pi:", pi)fmt.Printf("levels: debug=%d info=%d warn=%d error=%d\n",LevelDebug, LevelInfo, LevelWarn, LevelError)}
const pi = 3.14159 is a constant declared inside the function. If you try to reassign it anywhere, the program will refuse to compile. Use a constant whenever a value should never change at runtime, like a fixed ratio or a configuration limit.
The four Level* names come from a const block declared at the package level, outside any function:
go// main.gotype LogLevel intconst (LevelDebug LogLevel = iotaLevelInfoLevelWarnLevelError)
This is a common Go pattern for a small numbered list, often called an enum. First, we define LogLevel as a named type whose underlying type is int. Then the const block uses iota. This is a built-in counter that Go resets to 0 at the start of each const block and increments by one for every line. LevelDebug becomes 0. Because the following lines repeat the same expression by default, LevelInfo becomes 1, LevelWarn is 2, and LevelError is 3. The program prints exactly that:
levels: debug=0 info=1 warn=2 error=3
Using iota saves you from hand-numbering a list and re-numbering it every time you insert a new value in the middle. It is how Go developers typically build enums for log levels, statuses, or weekdays. This const block also illustrates the package-level rule from earlier. Because it sits outside any function, it uses type and const, never :=.
One rule catches almost everyone moving to Go from another language. If you declare a local variable and never use it, the program will not compile. It is not a warning you can ignore. It is a hard error: declared and not used.
gofunc demo() {total := 100fmt.Println("hello")}
That function fails to build because total is declared but never read. The same rule applies to imports you do not use. Go is strict here on purpose. An unused variable is usually a leftover from a half-finished edit or a typo where you assigned a value to the wrong name. Forcing you to remove it keeps the codebase clean. Our main.go compiles cleanly precisely because every variable it declares is also printed. When you hit a declared and not used error, the fix is almost always to delete the line or actually use the value.
You now have variables, types, and constants. These are the raw materials every Go program is built from. Next, we will group statements into functions and organize them into packages. We will also look at the multiple-return-value style that defines how Go handles errors.