Free Ebook cover Go (Golang) Fundamentals: Simple, Fast Programs for Beginners

Go (Golang) Fundamentals: Simple, Fast Programs for Beginners

New course

12 pages

Go Syntax Essentials: Variables, Types, Control Flow, and Functions

Capítulo 2

Estimated reading time: 6 minutes

+ Exercise

Variables and Constants

Go is explicit and predictable: you declare names (variables/constants), give them types (sometimes inferred), and assign values. Choosing between var, :=, and const is mostly about scope, clarity, and whether the value must never change.

var: Declare a Variable (Optionally With a Type)

Use var when you want an explicit type, when you need a package-level variable, or when you want to declare first and assign later.

package main

import "fmt"

func main() {
	var count int
	fmt.Println(count) // zero value: 0

	count = 3
	fmt.Println(count)

	var name = "gopher" // type inferred as string
	fmt.Println(name)
}

Zero Values (Default Values)

If you declare a variable without assigning a value, Go assigns a zero value based on the type. This makes code safer because you never read “uninitialized memory.”

  • int, float64: 0, 0.0
  • bool: false
  • string: "" (empty string)
  • rune (alias of int32): 0

Short Declaration :=: Declare + Assign Inside Functions

Inside a function, := is the most common way to create variables. It declares the variable and infers the type from the right-hand side.

func main() {
	count := 10      // int
	rate := 2.5      // float64
	active := true   // bool
	label := "demo" // string

	_ = count
	_ = rate
	_ = active
	_ = label
}

Rule of thumb: use := for local variables when the inferred type is obvious; use var when you want the type to stand out or you need a zero value first.

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

const: Compile-Time Constants

Use const for values that must not change and are known at compile time (numbers, strings, booleans). Constants improve readability and prevent accidental reassignment.

const maxRetries = 3
const appName = "Counter"

func main() {
	// maxRetries = 4 // compile error: cannot assign to const
}

When to choose each:

  • const: value must never change; configuration-like literals; sizes; fixed thresholds.
  • :=: most local variables; quick, clear initialization.
  • var: you need a specific type, a zero value first, or a variable outside a function.

Basic Types and Explicit Conversions

Go has a small set of basic types that you’ll use constantly. The key rule: Go does not do implicit numeric conversions; you must convert explicitly.

Integers and Floats

Common integer types include int (machine word size) and fixed-size types like int64. For floating point, float64 is the usual default.

var a int = 5
var b int64 = 10
var f float64 = 2.75

_ = a
_ = b
_ = f

Strings

Strings are immutable sequences of bytes. You can concatenate with + and get length with len (bytes, not characters).

s := "go"
t := s + "lang"
size := len(t)

_ = size

Booleans

bool values are true or false. Conditions in if and for must be boolean expressions (no “truthy” integers).

ready := true
if ready {
	// ...
}

Runes (Characters)

A rune represents a Unicode code point and is an alias for int32. Single quotes produce a rune literal.

r := 'A'        // rune
code := int32(r) // explicit conversion not required here, but shown for clarity

_ = code

Explicit Type Conversions Only

If you mix numeric types, convert explicitly. This is intentional: it prevents subtle bugs.

var x int = 3
var y int64 = 4

sum := int64(x) + y

var n int = int(2.9) // truncates toward zero: 2

_ = sum
_ = n

Conversions between string and numeric types are not done with simple casts. For example, converting "123" to an int requires parsing (covered later when you use standard library helpers).

Control Flow

Go keeps control flow small and consistent: if, for, switch, and a few keywords like defer for cleanup.

if (Including Short Statements)

An if can include a short statement before the condition. The variable declared there is scoped to the if/else block.

func isPositive(n int) bool {
	if n > 0 {
		return true
	}
	return false
}
func clampToMax(n, max int) int {
	if v := n; v > max {
		return max
	}
	return n
}

Step-by-step for the short statement form:

  • Run the short statement (v := n).
  • Evaluate the condition (v > max).
  • Execute the matching branch; v exists only inside that if/else.

for: Go’s Only Loop

Go uses for for all looping patterns: counting loops, while-style loops, and infinite loops. Use break and continue to control iteration.

func sumFirstN(n int) int {
	sum := 0
	for i := 1; i <= n; i++ {
		sum += i
	}
	return sum
}
func countdown(from int) {
	for from > 0 { // while-style
		from--
	}
}
func waitUntilReady() {
	for { // infinite loop
		ready := true
		if ready {
			break
		}
	}
}

switch: Clear Multi-Branch Logic

switch compares cases top-to-bottom and runs the first matching case. Unlike some languages, Go does not fall through by default.

func grade(score int) string {
	switch {
	case score >= 90:
		return "A"
	case score >= 80:
		return "B"
	case score >= 70:
		return "C"
	default:
		return "F"
	}
}

If you explicitly want fallthrough behavior, you must write fallthrough. It jumps to the next case body (it does not re-check the next case condition).

func describeDay(n int) string {
	switch n {
	case 6:
		return "Saturday"
	case 7:
		return "Sunday"
	default:
		return "Weekday"
	}
}
func demoFallthrough(x int) int {
	switch x {
	case 1:
		x += 10
		fallthrough
	case 2:
		x += 100
	default:
		x += 1000
	}
	return x
}

defer: Schedule Cleanup

defer schedules a function call to run when the surrounding function returns (whether it returns normally or early). This is commonly used for cleanup such as closing files or unlocking a mutex.

func doWork() int {
	resource := "acquired"
	_ = resource

	defer func() {
		// cleanup runs at function return
		resource = "released"
	}()

	return 42
}

Practical rule: place defer immediately after acquiring something that must be released, so the cleanup is hard to forget.

Functions

Functions are the main unit of organization in Go. They can take parameters, return values (including multiple values), and optionally name return values.

Parameters and Return Values

func add(a int, b int) int {
	return a + b
}

If adjacent parameters share a type, you can shorten the signature:

func add(a, b int) int {
	return a + b
}

Named Returns

You can name return values in the function signature. This can improve clarity for complex functions, but avoid overusing it. A named return variable starts with its zero value and can be returned with a bare return.

func splitSum(a, b int) (sum int, diff int) {
	sum = a + b
	diff = a - b
	return
}

Multiple Return Values

Multiple returns are used heavily in Go, especially for “value + ok” and “value + error” patterns.

func divMod(a, b int) (int, int) {
	q := a / b
	r := a % b
	return q, r
}

You can ignore a return value using the blank identifier _:

q, _ := divMod(10, 3)
_ = q

Result + Error: The Common Go Pattern

Instead of exceptions, Go typically returns an error as the second value. If the error is non-nil, the first value is often a zero value (or otherwise invalid) and should not be used.

type simpleError string

func (e simpleError) Error() string { return string(e) }

func parsePort(s string) (int, error) {
	if len(s) == 0 {
		return 0, simpleError("port is required")
	}
	port := 0
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c < '0' || c > '9' {
			return 0, simpleError("port must contain only digits")
		}
		port = port*10 + int(c-'0')
		if port > 65535 {
			return 0, simpleError("port out of range")
		}
	}
	if port == 0 {
		return 0, simpleError("port must be between 1 and 65535")
	}
	return port, nil
}

Step-by-step, this function behaves like a small CLI validator:

  • Reject empty input early.
  • Loop through each byte and ensure it is a digit.
  • Build the integer value incrementally.
  • Validate the numeric range.
  • Return (value, nil) on success, or (0, error) on failure.
func main() {
	port, err := parsePort("8080")
	if err != nil {
		// handle error
		return
	}
	_ = port
}

Now answer the exercise about the content:

In Go, why does a function like parsePort typically return (0, error) when validation fails?

You are right! Congratulations, now go to the next page

You missed! Try again.

Go often returns (value, error) instead of throwing exceptions. On failure, the function returns a non-nil error and typically uses the type’s zero value (like 0 for int) to indicate the result is invalid.

Next chapter

Packages and Imports in Go: Organizing Code with Clear Boundaries

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.