Back to Go Examples

Basic Go

A quick-reference and hands-on guide to the fundamentals of Go (Golang) programming.

How to use this page

The fastest way to keep what you learn is to teach it. The physicist Richard Feynman's trick was simple: if you can't explain something in plain words, you don't really understand it yet. For each section, work these four steps:

  1. Read the section once.
  2. Explain it out loud in plain words, as if teaching a friend — no jargon. If you stall, that's the gap.
  3. Do the exercise from a blank editor, without peeking. Where you get stuck is exactly what to reread.
  4. Reveal the solution, compare, and explain the idea again in your own words.

Watch for the 💡 Explain it simply prompts and ✎ Exercise boxes in each section. Hover any code block and click Copy to grab it.

Contents

  1. What is Go?
  2. Program Structure
  3. Variables & Data Types
  4. Operators
  5. Strings
  6. Arrays, Slices & Maps
  7. Control Structures
  8. Loops
  9. Functions
  10. Structs & Methods
  11. Interfaces
  12. Goroutines & Channels

1. What is Go?

A programming language is a precise, written notation for telling a computer what to do. Go was designed at Google in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson, and released publicly in 2009. Its authors were frustrated by large codebases that took many minutes to compile and were hard for new engineers to read, so they set out to build a language that is small enough to learn in a few days, compiles in seconds, and makes it easy to use every core of a modern processor.

Go is compiled: before a program runs, a tool called the compiler translates the entire source code into machine code, producing a single executable file. It is also statically typed: every value has a type (number, text, list, and so on) that is known and checked at compile time, so many mistakes are caught before the program ever runs. Today Go is widely used for web servers, command-line tools, and cloud infrastructure such as Docker and Kubernetes.

Source code
The human-readable text of a program, saved in files ending in .go.
Compiler
The program that translates source code into machine code the processor can execute.
Static typing
Checking that every value is used as the correct kind of data before the program runs.
Explain it simply

In one sentence, tell a friend what "compiled" and "statically typed" mean for a Go program — without using either of those words.

Reveal a plain-language answer

Before it runs, Go turns your whole program into a single machine-code file (compiled), and while doing that it checks that every value is used as the right kind of thing — a number where a number belongs, text where text belongs (statically typed). So many mistakes get caught up front, before the program ever starts, and it runs fast because the translating is already done.

2. Program Structure

Every Go program follows the same basic skeleton. Understanding that skeleton first makes every later example easier to read, because the only thing that changes is what goes inside it.

A Go source file begins with a package clause, which states which package the file belongs to. A package is a named group of related code; the special name main tells the compiler that this package builds into a runnable program rather than a library. Next come the import declarations, which list the other packages this file uses. For example, fmt (short for “format”) provides functions for printing text. Finally comes the code itself. Execution always begins at the function named main in package main. When main returns, the program ends.

Go uses curly braces { } to group statements into blocks, and the opening brace must sit on the same line as the statement that owns it. Comments, which are notes for human readers that the compiler ignores, start with // for a single line or are wrapped in /* … */ for several lines.

Package
A named collection of Go files that are compiled together.
Import
A declaration that makes another package's code available to this file.
Entry point
The function where execution starts: main() in package main.
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Compile and run:

go run main.go
go build -o myapp main.go   // produces a binary
./myapp
Common packages: fmt (I/O), math, strings, strconv, os, errors, net/http.
Exercise — your turn

Complete main so it greets the name in name. Use fmt.Printf with a %s placeholder and a \n newline to print: Hello, Ada! Welcome to Go.

package main

import "fmt"

func main() {
    name := "Ada"
    // TODO: print: Hello, Ada! Welcome to Go.
}
Show solution
package main

import "fmt"

func main() {
    name := "Ada"
    fmt.Printf("Hello, %s! Welcome to Go.\n", name)
}
Output
Hello, Ada! Welcome to Go.
Explain it simply

Why does every runnable Go program need both package main and func main()?

Reveal a plain-language answer

package main tells Go "this is a program you can run, not a library other code borrows from." func main() is the one function Go calls first when it starts. Together they say: this is runnable, and here is where to begin.

3. Variables & Data Types

A variable is a named storage location that holds a value while a program runs. You can think of it as a labelled box: the label is the variable's name, and the contents are its value. The contents can change, but the kind of thing the box holds cannot.

That “kind of thing” is the variable's data type. The type determines which values are allowed and which operations make sense. You can add two numbers, but you cannot meaningfully add a number to true. Go's basic types are integers (int, whole numbers), floating-point numbers (float64, numbers with a fractional part), strings (string, text), and booleans (bool, either true or false).

Go gives you two ways to declare a variable. The long form, var name type = value, states everything explicitly. The short form, name := value, lets the compiler infer the type from the value, and it can only be used inside functions. A variable that is declared without a value is not left undefined: Go assigns its zero value, which is 0 for numbers, "" for strings, and false for booleans. A constant, declared with const, is a value fixed at compile time that can never change.

Declaration
A statement that introduces a new variable name and its type.
Type inference
The compiler working out a variable's type from the value assigned to it.
Zero value
The default value a variable holds when none is given.
// var declaration — explicit type
var name   string  = "Alice"
var age    int     = 30
var price  float64 = 9.99
var active bool    = true

// Short variable declaration — type inferred (inside functions only)
city  := "NYC"
count := 10

// Multiple assignment
x, y := 1, 2

// Zero values — Go initialises all variables
var i int     // 0
var s string  // ""
var b bool    // false

// Constants
const Pi  = 3.14159
const Max = 100
TypeDescriptionZero value
int, int8/16/32/64Signed integers0
uint, uint8/16/32/64Unsigned integers0
float32, float64Floating-point numbers0
stringImmutable UTF-8 text""
boolBooleanfalse
byteAlias for uint80
runeAlias for int32 — Unicode code point0
Exercise — your turn

Declare age as an int using var, and city as a string using :=. Then print: age 30 in NYC.

package main

import "fmt"

func main() {
    // TODO: var age int = 30
    // TODO: city := "NYC"
    // TODO: print → age 30 in NYC
}
Show solution
package main

import "fmt"

func main() {
    var age int = 30
    city := "NYC"
    fmt.Printf("age %d in %s\n", age, city)
}
Output
age 30 in NYC
Explain it simply

Go gives every new variable a "zero value" (0, "", false). Explain why that's safer than leaving it undefined.

Reveal a plain-language answer

There's never leftover garbage in a fresh variable. A new number starts at 0, a new string is empty, a new bool is false. So reading a variable before you've set it is always safe and predictable — a whole family of "used an uninitialised value" bugs simply can't happen.

4. Operators

An operator is a symbol that tells the computer to perform a specific operation on one or more values, called operands. In a + b, the + is the operator and a and b are its operands. A combination of values and operators that produces a result is called an expression.

Go's operators fall into a few families. Arithmetic operators (+ - * / %) compute numbers; note that dividing two integers discards the remainder, so 10 / 3 is 3, while % (modulus) gives that remainder. Comparison operators (== != < > <= >=) compare two values and produce a boolean. Logical operators (&& for AND, || for OR, ! for NOT) combine booleans. Assignment operators (= += -= and so on) store a result in a variable.

Go is strict about types in expressions: both operands must be the same type, so adding an int to a float64 requires an explicit conversion such as float64(a). Also note that ++ and -- are statements in Go, not expressions, so you can write i++ on its own line but not x = i++.

Operand
A value an operator acts on.
Expression
Any combination of values and operators that evaluates to a single value.
Short-circuit evaluation
&& and || stop evaluating as soon as the result is known.
a, b := 10, 3
a + b    // 13
a - b    // 7
a * b    // 30
a / b    // 3  — integer division
a % b    // 1  — modulus

// Comparison
a == b   // false
a != b   // true
a <  b   // false
a >  b   // true

// Logical
a > 5 && b < 5   // true  — AND
a > 5 || b > 5   // true  — OR
!(a == b)         // true  — NOT

// Assignment shortcuts
a += 5
a++       // increment (statement only, not expression)
In Go, ++ and -- are statements, not expressions — you cannot write x = a++.
Exercise — your turn

Given a = 17 and b = 5, print the quotient and the remainder on one line, space-separated: 3 2.

package main

import "fmt"

func main() {
    a, b := 17, 5
    // TODO: print the quotient then the remainder → 3 2
}
Show solution
package main

import "fmt"

func main() {
    a, b := 17, 5
    fmt.Println(a/b, a%b)
}
Output
3 2
Explain it simply

In plain words, why is 7 / 2 equal to 3 in Go, and not 3.5?

Reveal a plain-language answer

Both 7 and 2 are whole numbers (integers), so Go does whole-number division and throws away the fractional part — you get 3, remainder 1. To keep the .5 you need a decimal on at least one side, like 7.0 / 2, which gives 3.5.

5. Strings

A string is a sequence of characters used to represent text: a name, a sentence, a line from a file. In Go a string is written between double quotes, as in "hello", or between backticks for a raw string that may span several lines and treats backslashes literally.

Under the hood, a Go string is a read-only sequence of bytes encoded in UTF-8, the standard encoding that can represent every alphabet and symbol. Plain English letters take one byte each, but characters such as é or emoji take several. This is why len(s) counts bytes, not characters. When you need to work with individual characters, Go calls them runes, and a for … range loop over a string steps through it one rune at a time.

Strings in Go are immutable: once created, a string's contents cannot be changed. Operations that seem to modify a string, such as joining two with + or converting to upper case, actually build and return a new string. The standard library's strings package provides common tools such as searching, splitting, and replacing, and strconv converts between strings and numbers.

Immutable
Cannot be modified after creation; changes produce a new value.
Byte
The basic 8-bit unit of storage; len() counts these.
Rune
A single Unicode character (code point), Go's rune type.
import (
    "fmt"
    "strings"
    "strconv"
)

first := "Alice"
last  := "Smith"

// Concatenation
first + " " + last                    // "Alice Smith"

// Sprintf — formatted string (like printf but returns a string)
fmt.Sprintf("Hello, %s! Age: %d", first, 30)

// strings package
strings.ToUpper("hello")             // "HELLO"
strings.ToLower("HELLO")             // "hello"
strings.TrimSpace("  hi  ")         // "hi"
strings.Contains("hello", "ell")    // true
strings.Split("a,b,c", ",")         // ["a","b","c"]

// Convert between strings and numbers
strconv.Itoa(42)                      // "42"
strconv.Atoi("42")                    // 42, nil
Exercise — your turn

Join first and last with a space, then print the result in UPPERCASE using the strings package: ADA LOVELACE.

package main

import (
    "fmt"
    "strings"
)

func main() {
    first, last := "ada", "lovelace"
    // TODO: build "ada lovelace", then print it uppercased → ADA LOVELACE
}
Show solution
package main

import (
    "fmt"
    "strings"
)

func main() {
    first, last := "ada", "lovelace"
    full := first + " " + last
    fmt.Println(strings.ToUpper(full))
}
Output
ADA LOVELACE
Explain it simply

Strings in Go are immutable. Does strings.ToUpper(full) change full itself? Explain.

Reveal a plain-language answer

No. ToUpper can't edit the original — strings can't be changed in place. It builds and hands back a brand-new string. full stays exactly as it was unless you assign the new string back onto it.

6. Arrays, Slices & Maps

Programs rarely deal with just one value at a time. A collection is a data structure that holds many values under a single name. Go provides three built-in collections, and choosing the right one is one of the most common design decisions you will make.

An array holds a fixed number of elements of the same type, stored side by side in memory. Its length is part of its type, so [3]int and [4]int are different types, and the length can never change. Each element is reached by its index, its position counting from zero. Because they are rigid, arrays are used directly less often than the next structure.

A slice is a flexible, resizable view onto an underlying array, and it is Go's everyday list. You can grow a slice with append, and take a portion of it with s[low:high]. A map is an unordered collection of key–value pairs: instead of looking a value up by numeric position, you look it up by a key, such as a person's name to find their age. Maps give very fast lookups, but their iteration order is deliberately unpredictable.

Index
An element's position in an array or slice, starting at 0.
Capacity
How many elements a slice can hold before append must allocate a larger array.
Key–value pair
An entry in a map: a unique key associated with one value.

Arrays — fixed size

var a [3]int = [3]int{10, 20, 30}
a[0]          // 10
len(a)        // 3

Slices — dynamic, use these in practice

fruits := []string{"apple", "banana", "cherry"}

fruits[0]                        // "apple"
fruits[1:3]                      // ["banana","cherry"] — slice of slice
len(fruits)                      // 3

fruits = append(fruits, "date") // append — may allocate new array

// Make a slice with length and capacity
s := make([]int, 5)              // [0,0,0,0,0]

Maps

ages := map[string]int{
    "Alice": 30,
    "Bob":   25,
}

ages["Alice"]                    // 30
ages["Carol"] = 28               // add / update
delete(ages, "Bob")              // remove key

// Safe lookup — ok is false if key missing
val, ok := ages["Dave"]
if !ok {
    fmt.Println("not found")
}
Exercise — your turn

Append 4 and 5 to the slice, then print the slice and its length: [1 2 3 4 5] 5.

package main

import "fmt"

func main() {
    nums := []int{1, 2, 3}
    // TODO: append 4 and 5 to nums, then print nums and len(nums)
}
Show solution
package main

import "fmt"

func main() {
    nums := []int{1, 2, 3}
    nums = append(nums, 4, 5)
    fmt.Println(nums, len(nums))
}
Output
[1 2 3 4 5] 5
Explain it simply

Why must you write nums = append(nums, 4) and not just call append(nums, 4) and ignore the result?

Reveal a plain-language answer

When a slice runs out of room, append makes a bigger array behind the scenes and returns a slice pointing at the new one. If you throw that return value away, your variable still points at the old, smaller array — and never sees the item you added. So you must catch what append hands back.

7. Control Structures

By default a program runs its statements one after another, top to bottom. Control structures let a program make decisions, running some statements only when certain conditions hold. This is what lets software react differently to different inputs.

The most basic decision is the if statement. It evaluates a condition, an expression that is either true or false, and runs its block only when the condition is true. An optional else if tests further conditions in turn, and a final else handles every remaining case. In Go the condition is never wrapped in parentheses, but the braces around each block are always required. An if may also start with a short statement, such as if v, err := f(); err != nil, which is the idiomatic way to check for errors.

When one value must be compared against many possibilities, a switch statement is clearer than a long if/else if chain. Go's switch checks each case from top to bottom and runs only the first one that matches. Unlike C or Java, Go does not “fall through” into the next case, so no break is needed.

Condition
A boolean expression that decides which branch runs.
Branch
One of the alternative blocks a control structure can choose.
Fallthrough
Continuing into the next case; Go does this only if you write fallthrough.

if / else if / else

score := 75

if score >= 90 {
    fmt.Println("A")
} else if score >= 75 {
    fmt.Println("B")
} else {
    fmt.Println("C or below")
}

switch

// No fallthrough by default — no break needed
switch day {
case "Mon":
    fmt.Println("Monday")
case "Fri", "Sat":
    fmt.Println("End of week")
default:
    fmt.Println("Other")
}
Exercise — your turn

Print the letter grade using if / else if / else: A for score ≥ 90, B for ≥ 80, otherwise C. Here score is 82, so it should print B.

package main

import "fmt"

func main() {
    score := 82
    // TODO: print "A", "B", or "C"
}
Show solution
package main

import "fmt"

func main() {
    score := 82
    if score >= 90 {
        fmt.Println("A")
    } else if score >= 80 {
        fmt.Println("B")
    } else {
        fmt.Println("C")
    }
}
Output
B
Explain it simply

What does a switch give you that a long if / else if chain doesn't?

Reveal a plain-language answer

It reads cleaner when you're checking one thing against many possibilities, and Go's switch doesn't "fall through" into the next case, so you don't sprinkle break everywhere. A condition-less switch { case x > 90: ... } can even stand in for an if/else chain while staying tidy.

8. Loops

A loop repeats a block of code, either a fixed number of times or until some condition changes. Repetition is fundamental to programming: processing every line of a file, every item in a list, or every request to a server all rely on loops. Each pass through the loop body is called an iteration.

Most languages offer several loop keywords (for, while, do), but Go has exactly one, for, which takes several forms. The three-part form, for init; condition; post, counts through a range of numbers. The condition-only form, for condition, behaves like a while loop in other languages. The bare form, for { }, loops forever until something inside stops it. The range form steps through each element of a slice, map, string, or channel.

Two keywords adjust a loop from the inside: break exits the loop immediately, and continue skips the rest of the current iteration and moves on to the next. A loop whose condition never becomes false is an infinite loop. That is sometimes intended, as in a server waiting for connections, but often it is a bug.

Iteration
A single pass through the body of a loop.
Loop variable
The variable that changes each iteration, such as the counter i.
range
The keyword that iterates over the elements of a collection.
Go has only one loop keyword: for. It covers all looping patterns.

Traditional for

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

while-style

i := 0
for i < 5 {
    fmt.Println(i)
    i++
}

range — iterate over slices, maps, strings

fruits := []string{"apple", "banana", "cherry"}

for i, fruit := range fruits {
    fmt.Printf("%d: %s\n", i, fruit)
}

// Ignore index with _
for _, fruit := range fruits {
    fmt.Println(fruit)
}
Use break to exit a loop and continue to skip to the next iteration.
Exercise — your turn

Use a for loop to add up the numbers 1 through 10, then print the total: 55.

package main

import "fmt"

func main() {
    sum := 0
    // TODO: loop i from 1 to 10, add each to sum, then print sum → 55
}
Show solution
package main

import "fmt"

func main() {
    sum := 0
    for i := 1; i <= 10; i++ {
        sum += i
    }
    fmt.Println(sum)
}
Output
55
Explain it simply

Go has only for — no while keyword. Explain how one keyword still covers a while loop.

Reveal a plain-language answer

for has three optional parts: a start, a condition, and a step. Keep only the condition — for sum < 100 { } — and it behaves exactly like a while. Drop all three — for { } — and it loops forever until you break. Same keyword, you just leave out the pieces you don't need.

9. Functions

A function is a named, reusable block of code that performs one specific task. Functions are the primary way programmers break a large problem into small, understandable pieces: instead of writing the same steps in many places, you write them once, give them a name, and call that name wherever the task is needed.

A function can receive input through parameters, variables listed in parentheses after its name, each with a type. The actual values passed in when the function is called are its arguments. A function can hand results back to its caller with a return statement, and its signature declares the types of those results. A distinctive feature of Go is that a function may return multiple values. By convention, a function that can fail returns its result together with an error value, and the caller checks whether that error is nil.

Go passes arguments by value, meaning the function receives a copy, so changing a parameter inside the function does not affect the caller's variable. To let a function modify the original, you pass a pointer, the memory address of the variable. Functions in Go are also first-class values: they can be stored in variables, passed to other functions, and written inline as anonymous functions, or closures, that remember the variables around them.

Parameter / argument
The variable in the definition / the actual value supplied in the call.
Signature
A function's name, parameter types, and return types.
Closure
A function value that captures variables from the scope where it was created.
// Basic function
func greet(name string) string {
    return "Hello, " + name + "!"
}

// Multiple return values — idiomatic Go
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("cannot divide by zero")
    }
    return a / b, nil
}

result, err := divide(10, 3)
if err != nil {
    fmt.Println("Error:", err)
}

// defer — run at end of the surrounding function
defer fmt.Println("cleanup")   // runs when function returns
Returning an error as the last return value is the standard Go pattern for error handling — there are no exceptions.
Exercise — your turn

Finish divide: return an error when b is 0, otherwise return a/b and nil. Called with (10, 2), the program should print Result: 5.

package main

import (
    "errors"
    "fmt"
)

func divide(a, b int) (int, error) {
    // TODO: if b == 0, return 0 and errors.New("divide by zero")
    // TODO: otherwise return a/b and nil
}

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}
Show solution
package main

import (
    "errors"
    "fmt"
)

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("divide by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}
Output
Result: 5
Explain it simply

Go has no exceptions. Explain how returning an error value replaces try / catch.

Reveal a plain-language answer

A Go function hands back an error as its last return value. Right after you call it, you check if err != nil and decide what to do. Errors are just ordinary values moving through your normal code — nothing is secretly thrown and caught somewhere far away.

10. Structs & Methods

Real-world things have several properties at once. A person has a name and an age, and a bank account has an owner and a balance. A struct (short for “structure”) is a user-defined type that groups several related values, called fields, into a single unit. Where a slice holds many values of the same type, a struct holds a fixed set of named values that may each have a different type.

You define a struct with type Name struct { … }, create a value of it with a struct literal such as Person{Name: "Alice", Age: 30}, and read or change its fields with a dot, as in p.Name. A field whose name starts with a capital letter is exported, meaning visible to other packages; a lower-case name keeps it private to its own package.

A method is a function attached to a particular type. It is declared with a receiver written before the method name, as in func (p Person) Greet(). A value receiver works on a copy of the struct, while a pointer receiver (func (p *Person)) works on the original and can therefore modify it. Go has no classes or inheritance. Structs and methods, combined with interfaces in the next section, fill that role.

Field
A named value inside a struct.
Receiver
The value a method is called on, declared before the method's name.
Exported
Visible outside its package; indicated by a capitalized name.
// Define a struct
type Person struct {
    Name string
    Age  int
}

// Create instances
p1 := Person{Name: "Alice", Age: 30}

p1.Name                                // "Alice"
p1.Age = 31                           // update field

// Method — function with a receiver
func (p Person) Describe() string {
    return fmt.Sprintf("%s is %d years old.", p.Name, p.Age)
}

// Pointer receiver — can modify the struct
func (p *Person) Birthday() {
    p.Age++
}
Exercise — your turn

Give Person a Describe() method (use fmt.Sprintf) so the program prints Ada is 30 years old.

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

// TODO: add a Describe() string method on Person

func main() {
    p := Person{Name: "Ada", Age: 30}
    fmt.Println(p.Describe())
}
Show solution
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func (p Person) Describe() string {
    return fmt.Sprintf("%s is %d years old.", p.Name, p.Age)
}

func main() {
    p := Person{Name: "Ada", Age: 30}
    fmt.Println(p.Describe())
}
Output
Ada is 30 years old.
Explain it simply

Explain the difference between a value receiver (p Person) and a pointer receiver (p *Person).

Reveal a plain-language answer

A value receiver works on a copy of the struct, so any changes it makes vanish when the method returns. A pointer receiver works on the original, so it can actually change the struct's fields (and it avoids copying a big struct). Reach for a pointer receiver whenever the method needs to modify the thing.

11. Interfaces

An interface is a type that describes behaviour rather than data. It lists a set of method signatures, which are the names, parameters, and return types of methods, without saying how any of them work. Any type that has all of those methods is said to satisfy (or implement) the interface, and a value of that type can be used wherever the interface is expected.

Interfaces make polymorphism possible: writing one piece of code that works with many different types. A function that accepts a Shape interface can compute the area of a circle, a rectangle, or any shape invented later, without knowing which concrete type it has been given. This keeps code flexible and makes it easy to substitute one implementation for another, for example a fake database in tests.

Go's interfaces are implicit. A type never declares “I implement Shape”; if it has the right methods, it simply does. This is sometimes called structural typing or “duck typing checked at compile time”: if it walks like a duck and quacks like a duck, the compiler accepts it as a duck. Good Go interfaces tend to be small, often a single method, like the standard library's io.Reader and fmt.Stringer. The empty interface, any, has no methods and is therefore satisfied by every type.

Method set
The collection of methods a type has, which determines which interfaces it satisfies.
Polymorphism
One piece of code working with values of many different types.
Type assertion
v.(T): recovering the concrete type stored in an interface value.
type Shape interface {
    Area()      float64
}

type Circle struct { Radius float64 }

func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }

// Circle satisfies Shape automatically
func printArea(s Shape) {
    fmt.Printf("Area: %.2f\n", s.Area())
}
Exercise — your turn

Make Circle satisfy Shape by giving it an Area() method (π·r·r). For a radius-2 circle, the program should print 12.57.

package main

import (
    "fmt"
    "math"
)

type Shape interface {
    Area() float64
}

type Circle struct {
    Radius float64
}

// TODO: implement Area() float64 for Circle

func main() {
    var s Shape = Circle{Radius: 2}
    fmt.Printf("%.2f\n", s.Area())
}
Show solution
package main

import (
    "fmt"
    "math"
)

type Shape interface {
    Area() float64
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func main() {
    var s Shape = Circle{Radius: 2}
    fmt.Printf("%.2f\n", s.Area())
}
Output
12.57
Explain it simply

Go interfaces are "implicit." Explain what that means compared with languages where you write implements Shape.

Reveal a plain-language answer

You never announce that Circle implements Shape. The instant Circle has the method the interface asks for — Area() float64 — it automatically counts as a Shape. Fitting an interface is about having the right methods, not declaring your intention to fit it.

12. Goroutines & Channels

So far every program has done one thing at a time. Concurrency is the ability to have several tasks in progress at once, such as serving many web visitors, downloading several files, or keeping a user interface responsive during a long calculation. On a machine with multiple processor cores, concurrent tasks can also run truly in parallel, at the same instant.

A goroutine is Go's unit of concurrency: a function running independently alongside the rest of the program. You start one simply by writing go before a function call. Goroutines are far lighter than operating-system threads, since each starts with only a few kilobytes of memory, so a program can comfortably run thousands of them. The Go runtime schedules them across the available CPU cores automatically. When main returns, the program exits, even if other goroutines are still running, so you must wait for work to finish, for example with a sync.WaitGroup.

A channel is a typed pipe that lets goroutines communicate safely. One goroutine sends a value with ch <- v, and another receives it with v := <-ch. On an unbuffered channel, the sender waits until a receiver is ready, so the handoff also synchronizes the two goroutines. Go's guiding motto is: “Do not communicate by sharing memory; instead, share memory by communicating.” Passing data through channels, rather than having many goroutines modify the same variables, avoids a whole class of bugs called race conditions.

Concurrency vs. parallelism
Dealing with many tasks at once / literally executing them at the same instant.
Buffered channel
A channel with capacity, make(chan T, n), whose sends do not block until it is full.
Race condition
A bug where the result depends on the unpredictable timing of goroutines accessing shared data.

Goroutines

go say("world")   // launches a goroutine — runs alongside main
say("hello")      // runs in the main goroutine

Channels

// Create a channel of ints
ch := make(chan int)

// Send a value in a goroutine
go func() {
    ch <- 42   // send
}()

val := <-ch   // receive (blocks until value arrives)
Go's concurrency motto: "Do not communicate by sharing memory; instead, share memory by communicating." Prefer channels over shared variables.
Exercise — your turn

Start a goroutine that sends 42 into the channel, then receive it in main and print it: 42.

package main

import "fmt"

func main() {
    ch := make(chan int)
    // TODO: start a goroutine that sends 42 into ch
    // TODO: receive from ch into a variable named val
    // TODO: print val
}
Show solution
package main

import "fmt"

func main() {
    ch := make(chan int)
    go func() {
        ch <- 42
    }()
    val := <-ch
    fmt.Println(val)
}
Output
42
Explain it simply

Explain why receiving from an unbuffered channel "blocks," and why that's actually useful.

Reveal a plain-language answer

An unbuffered channel passes a value hand-to-hand: the receiver waits until some goroutine sends, and the sender waits until someone is there to receive. That built-in waiting lines the two goroutines up at the same moment — it synchronizes them for you, so you don't need locks to coordinate.