Go Programming Language — Syntax and Data Types
Go Programming Language — Syntax and Data Types

In this post I’ll cover the fundamental building blocks of Go: syntax, types, keywords, variable declarations, constants, and UTF-8 support — with examples throughout.
Go has exactly 25 keywords — one of the fewest of any mainstream language. For comparison:
- C++: 82
- C#: 79
- Java: 50
- JavaScript: 38
- Python 3.7: 35
- Dart: 54
The complete Go keyword list:
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import
There are also 47 operators and punctuation marks:
+ & += &= && == != ( )
- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^=
Variable Declaration
Go offers several ways to declare variables. The first is declaring without an initial value — the variable is automatically initialized to the zero value for its type:
var t int
You can also assign a value at declaration time using the = operator:
var t int = 10
The short variable declaration (:=) is a concise form that requires an initial value and infers the type. It creates a local variable scoped to the enclosing block — a function, loop, or other block:
t := 10
To explicitly specify a type during short declaration, wrap the value in a type conversion:
t := int64(10)
If a variable is declared without an initial value, Go assigns the zero value for its type:
- Numeric types:
0 - Boolean:
false - String:
""(empty string)
Unlike some languages, Go has no undefined value. A bool, for instance, is always either true or false.
package main
import "fmt"
func main() {
var name string
fmt.Printf("var name %T = %+v\n", name, name)
var payment float64
fmt.Printf("var payment %T = %q\n", payment, payment)
var isActive bool
fmt.Printf("var isActive %T = %+v\n", isActive, isActive)
}
Output:
var name string = ""
var payment float64 = 0
var isActive bool = false
Variable Naming
Go does not have private, public, or protected access modifiers. Instead, visibility is determined by the case of the first letter of an identifier:
If the name starts with an uppercase letter, it is exported (accessible from other packages). If it starts with a lowercase letter, it is unexported (package-private).
This makes Go identifiers case-sensitive by design.
var FirstName string // exported
var password string // unexported
Additional naming rules:
- Use only letters, digits, and underscores:
// Valid: user_name, userName, signal4
// Invalid: best.offer, user-name
- Must not start with a digit:
// Valid: best4Game, house62
// Invalid: 10cars
- No symbols:
// Valid: product
// Invalid: $product
Multiple Assignment
Go allows assigning values to multiple variables of different types in a single statement — a feature that greatly improves code readability:
a, b, c := 12, true, "Google"
fmt.Println(a)
fmt.Println(c)
fmt.Println(b)
Output:
12
Google
true
Numeric Types
Go has two categories of numeric types: architecture-independent (fixed-size) and implementation-specific (size depends on the target platform).
Architecture-independent types:
uint8 unsigned 8-bit integer (0–255)
uint16 unsigned 16-bit integer (0–65535)
uint32 unsigned 32-bit integer (0–4294967295)
uint64 unsigned 64-bit integer (0–18446744073709551615)
int8 signed 8-bit integer (-128–127)
int16 signed 16-bit integer (-32768–32767)
int32 signed 32-bit integer (-2147483648–2147483647)
int64 signed 64-bit integer (-9223372036854775808–9223372036854775807)
float32 IEEE-754 32-bit floating-point (+- 1e-45 -> +- 3.4e38)
float64 IEEE-754 64-bit floating-point (+- 5e-324 -> 1.7e308)
complex64
complex128
byte ~~ uint8
rune ~~ int32
Implementation-specific types:
uint -> 32 or 64 bits
int -> same size as uint
uintptr -> an unsigned integer large enough to hold any pointer value
Choosing the right fixed-size type matters for performance. If you only need values in the range 0–200, prefer uint8 over uint.
Example 1: Value within range — correct result:
package main
import (
"fmt"
)
func main() {
var t uint8 = 254
fmt.Println(t + 1)
}
Output:
255
Example 2: Value overflows at print time — wraps to 0:
package main
import (
"fmt"
)
func main() {
var t uint8 = 254
fmt.Println(t + 2)
}
Output:
0
Example 3: Overflow detected at compile time — compiler error:
package main
import (
"fmt"
)
func main() {
var t uint8 = 254 + 2
fmt.Println(t)
}
Output:
./prog.go:8:20: constant 256 overflows uint8
Boolean
The bool type has two possible values: true and false. Its zero value is false. Booleans are commonly used in control flow:
package main
import (
"fmt"
)
func main() {
var isActive bool
printed := true
fmt.Println(isActive)
fmt.Println(printed)
}
Output:
false
true
String
A string is an ordered sequence of one or more characters. In Go, string literals can be written with double quotes (") or raw string literals with backticks (`):
a := "He said \"Hello\" this morning"
b := `He said "Hello" this morning`
UTF-8
Go has native UTF-8 support built into the language — no external package or library required.
You can test this with the following code:
package main
import (
"fmt"
)
func main() {
a := "Hello, 世界"
for i, c := range a {
fmt.Printf("%d: %s\n", i, string(c))
}
fmt.Println("length of 'Hello, 世界': ", len(a))
}
Output:
0: H
1: e
2: l
3: l
4: o
5: ,
6:
7: 世
10: 界
length of 'Hello, 世界': 13
The reported length is greater than the character count because of Go’s rune type. A rune holds a character’s Unicode code point and is represented as 1–3 int32 values.
package main
import "fmt"
func main() {
const nihongo = "日本語"
for index, runeValue := range nihongo {
fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
}
}
Output:
U+65E5 '日' starts at byte position 0
U+672C '本' starts at byte position 3
U+8A9E '語' starts at byte position 6
Constants
Constants are immutable values — once assigned, they cannot be changed:
package main
import "fmt"
func main() {
const gopher = "Nail"
fmt.Println(gopher)
}
Output:
Nail
Attempting to reassign a constant causes a compile-time error:
package main
import "fmt"
func main() {
const gopher = "Nail"
fmt.Println(gopher)
gopher = "Can"
fmt.Println(gopher)
}
Output:
./prog.go:10:9: cannot assign to gopher
Typed and Untyped Constants
Constants can be declared with or without an explicit type. An untyped constant takes on a type implicitly based on its value at the point of use:
package main
import "fmt"
const (
year = 365 // untyped
leapYear = int32(366) // typed
)
func main() {
hours := 24
minutes := int32(60)
fmt.Println(hours * year)
fmt.Println(minutes * year)
fmt.Println(minutes * leapYear)
}
Output:
8760
21900
21960
Assigning a value of the wrong type to a typed constant results in a compile-time error:
package main
import "fmt"
const (
leapYear = int32("test") // typed
)
func main() {
minutes := 10
fmt.Println(minutes * leapYear)
}
Output:
./prog.go:6:18: cannot convert "test" (type untyped string) to type int32
Iota
iota is a special constant generator used to create a sequence of incrementing values. Within a const block, iota starts at 0 and increments by 1 for each constant:
package main
import "fmt"
const (
Metin int = iota
Ali
Feyyaz
)
func main() {
fmt.Printf("Metin is %v\n", Metin)
fmt.Printf("Feyyaz is %v\n", Feyyaz)
fmt.Printf("Ali is %v\n", Ali)
}
Output:
Metin is 0
Feyyaz is 2
Ali is 1
Console Output
Go’s fmt package provides formatted I/O. After importing it, you can use Print, Printf, and Println (among others) to produce output:
- Use
Printlnfor plain string output. - Use
Printfwhen you need to format values inline using verbs.
package main
import "fmt"
func main() {
fmt.Println("This code was already here when I arrived.")
fmt.Printf("It's always %s fault.\n", "the previous developer's")
}
Output:
This code was already here when I arrived.
It's always the previous developer's fault.
Format Verbs
All Print functions support format verbs. Common ones:
// %v -> prints the value without type checking
// %s -> for string values
// %d -> for integer values (int, int32, etc.)
// %T -> prints the type of the value
// %f -> for floating-point values
// \n -> newline
// \t -> tab character
Example:
package main
import (
"fmt"
)
func main() {
const name, age, isWorking, mealCost = "Nail", 32, true, 21.5
fmt.Printf("Name: %v \n", name)
fmt.Printf("Age: %v \n", age)
fmt.Printf("Is working: %v \n", isWorking)
fmt.Printf("Meal cost: %v", mealCost)
fmt.Println("********")
fmt.Printf("Name: %s \n", name)
fmt.Printf("Age: %d \n", age)
fmt.Printf("Is working: %t \n", isWorking)
fmt.Printf("Meal cost: %f", mealCost)
fmt.Println("********")
fmt.Printf("Name: %T \n", name)
fmt.Printf("\t Age: %T \n", age)
fmt.Printf("\t \t Is working: %T \n", isWorking)
fmt.Printf("\t \t \t Meal cost: %T", mealCost)
}
Output:
Name: Nail
Age: 32
Is working: true
Meal cost: 21.5********
Name: Nail
Age: 32
Is working: true
Meal cost: 21.500000********
Name: string
Age: int
Is working: bool
Meal cost: float64
For the full fmt package reference, see the official Go documentation.
Comments
Go uses the same comment syntax as most C-family languages:
// Single-line comment
For block comments:
/*
All of these lines
are
comments
*/
Inline comments are also valid:
z := x % 2 // returns the remainder of x divided by 2
Go also uses comments to generate documentation. GoDoc parses the comments immediately above functions and types to build API documentation, so writing clear and accurate comments is especially important in Go.
See the Go Blog post on GoDoc for more details.
Structs
A struct is a composite data type that groups together fields under a single name — similar to a model or record in other languages. Structs are defined using the type keyword:
type User struct {
Name string
Email string
}
Fields are accessed using dot notation. You can read a field’s value or assign a new one with =.
All of the following are valid ways to instantiate a struct:
a := User{}
b := User{
Name: "Homer Simpson",
Email: "homer@example.com",
}
c := User{Email: "marge@example.com"}
c.Name = "Marge Simpson"
d := User{Name: "Homer Simpson", Email: "homer@example.com"}
e := User{"Homer Simpson", "homer@example.com"}
fmt.Println(a.Name)
fmt.Printf("User's email is %s", e.Email)
However, the positional initialization used for e is discouraged. If the struct definition changes — say, a new field is added — the positional form will break:
type User struct {
ID int
Name string
Email string
}
After this change, the positional initialization produces:
too few values in User literal
The notes shared here are primarily derived from materials used in Google’s Go workshops and from the official Go documentation and Tour.