Go (or Golang) is an open-source programming language developed by Google. It is statically typed, compiled, and designed for simplicity, efficiency, and high performance.
Download from the official website golang.org/dl and follow installation instructions for your OS.
Let's write a simple program that prints "Hello, World!" to the console.
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Explanation:
package main
: Defines the package; main package tells Go this is an executable program.import "fmt"
: Imports the "fmt" package, which contains functions for formatted I/O, including printing.func main()
: Defines the main function, entry point of the program.fmt.Println("Hello, World!")
: Prints the text to the console with a newline.Save the file as hello.go
, then run:
go run hello.go
This will compile and execute your Go program.
Go is statically typed, so variables have types. Here's how to declare and use variables:
package main
import "fmt"
func main() {
var name string = "Alice" // Declare string variable
age := 30 // Short declaration with inferred type
fmt.Println("Name:", name)
fmt.Println("Age:", age)
}
Explanation:
var name string = "Alice"
: Declares a variable named name
of type string
.age := 30
: Short-hand for variable declaration and assignment; Go infers the type.fmt.Println
: Prints the variables with spaces between arguments.Common Go types include:
int
: Integer numbersfloat64
: Floating point numbersstring
: Textbool
: Boolean true/falseConstants are immutable values:
const Pi = 3.14159
const Greeting = "Hello!"
Use //
for single-line comments and /* ... */
for multi-line comments.
// This is a single-line comment
/*
This is a
multi-line comment
*/
The if
statement is used to run code conditionally based on a boolean expression.
package main
import "fmt"
func main() {
age := 20
if age >= 18 {
fmt.Println("You are an adult.")
}
}
if-else
runs one block if the condition is true, another if false.
package main
import "fmt"
func main() {
age := 16
if age >= 18 {
fmt.Println("Adult")
} else {
fmt.Println("Minor")
}
}
Use multiple conditions with else if
.
package main
import "fmt"
func main() {
score := 85
if score >= 90 {
fmt.Println("Grade A")
} else if score >= 75 {
fmt.Println("Grade B")
} else {
fmt.Println("Grade C")
}
}
Switch is an elegant way to replace multiple if-else checks.
package main
import "fmt"
func main() {
day := "Monday"
switch day {
case "Monday":
fmt.Println("Start of week")
case "Friday":
fmt.Println("End of week")
default:
fmt.Println("Midweek")
}
}
You can combine cases by separating values with commas.
package main
import "fmt"
func main() {
ch := 'a'
switch ch {
case 'a', 'e', 'i', 'o', 'u':
fmt.Println("Vowel")
default:
fmt.Println("Consonant")
}
}
Switch can be used as an alternative to if-else by omitting the expression.
package main
import "fmt"
func main() {
num := 7
switch {
case num < 0:
fmt.Println("Negative")
case num == 0:
fmt.Println("Zero")
case num > 0:
fmt.Println("Positive")
}
}
The only loop type in Go: classic initialization; condition; post statement.
package main
import "fmt"
func main() {
for i := 1; i <= 5; i++ {
fmt.Println("Number:", i)
}
}
For loop can be used like a while loop by omitting init and post.
package main
import "fmt"
func main() {
i := 1
for i <= 3 {
fmt.Println("Counting:", i)
i++
}
}
Loop with no condition runs forever unless broken.
package main
import "fmt"
func main() {
count := 0
for {
if count >= 3 {
break
}
fmt.Println("Loop", count)
count++
}
}
continue
skips the rest of current iteration, break
exits loop.
package main
import "fmt"
func main() {
for i := 1; i <= 5; i++ {
if i == 3 {
continue
}
if i == 5 {
break
}
fmt.Println("Number:", i)
}
}
Functions group reusable code blocks that can be called by name.
package main
import "fmt"
func greet() {
fmt.Println("Hello, World!")
}
func main() {
greet()
}
Functions can accept inputs called parameters.
package main
import "fmt"
func greet(name string) {
fmt.Println("Hello,", name)
}
func main() {
greet("Alice")
}
Functions can return results using the return
keyword.
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func main() {
sum := add(3, 5)
fmt.Println("Sum:", sum)
}
Go functions can return multiple values.
package main
import "fmt"
func swap(a, b string) (string, string) {
return b, a
}
func main() {
x, y := swap("first", "second")
fmt.Println(x, y)
}
You can name return variables and omit them in the return
statement.
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
a, b := split(100)
fmt.Println(a, b)
}
Functions that accept variable number of arguments.
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3, 4))
}
Functions without names, useful for inline use.
package main
import "fmt"
func main() {
f := func(msg string) {
fmt.Println(msg)
}
f("Hello from anonymous function")
}
Functions can be passed as parameters to other functions.
package main
import "fmt"
func apply(f func(int) int, x int) int {
return f(x)
}
func square(x int) int {
return x * x
}
func main() {
result := apply(square, 5)
fmt.Println(result)
}
Functions can return other functions.
package main
import "fmt"
func multiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
double := multiplier(2)
fmt.Println(double(5))
}
defer
schedules a function call to run after the current function finishes.
package main
import "fmt"
func main() {
defer fmt.Println("World")
fmt.Println("Hello")
}
Fixed-size collections of elements of the same type.
package main
import "fmt"
func main() {
var a [3]int = [3]int{1, 2, 3}
fmt.Println(a)
}
Use len()
to get the length of an array.
package main
import "fmt"
func main() {
a := [3]int{10, 20, 30}
fmt.Println("Length:", len(a))
}
Dynamic-sized, flexible view into arrays.
package main
import "fmt"
func main() {
s := []int{1, 2, 3}
fmt.Println(s)
}
Extract portions of an array or slice.
package main
import "fmt"
func main() {
arr := [5]int{10, 20, 30, 40, 50}
slice := arr[1:4]
fmt.Println(slice)
}
Use append()
to add elements.
package main
import "fmt"
func main() {
s := []int{1, 2}
s = append(s, 3, 4)
fmt.Println(s)
}
Use copy()
to copy elements from one slice to another.
package main
import "fmt"
func main() {
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
fmt.Println(dst)
}
Key-value data structures.
package main
import "fmt"
func main() {
m := map[string]int{"a": 1, "b": 2}
fmt.Println(m)
}
Add and read map entries by key.
package main
import "fmt"
func main() {
m := make(map[string]int)
m["x"] = 10
fmt.Println(m["x"])
}
Use delete()
to remove keys.
package main
import "fmt"
func main() {
m := map[string]int{"a": 1, "b": 2}
delete(m, "a")
fmt.Println(m)
}
Check if a key exists.
package main
import "fmt"
func main() {
m := map[string]int{"a": 1}
val, ok := m["a"]
fmt.Println(val, ok) // 1 true
val2, ok2 := m["b"]
fmt.Println(val2, ok2) // 0 false
}
A struct is a composite data type grouping variables under one name.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
fmt.Println(p)
}
You can read or modify struct fields with dot notation.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Bob", Age: 25}
fmt.Println(p.Name)
p.Age = 26
fmt.Println(p.Age)
}
Create struct values using literals with or without field names.
package main
import "fmt"
type Point struct { X, Y int }
func main() {
p1 := Point{10, 20} // without field names
p2 := Point{X: 5, Y: 7} // with field names
fmt.Println(p1, p2)
}
You can have pointers to structs and modify fields via pointers.
package main
import "fmt"
type Person struct { Name string }
func main() {
p := Person{Name: "Eve"}
ptr := &p
ptr.Name = "Eva"
fmt.Println(p.Name)
}
Pass structs to functions by value or pointer.
package main
import "fmt"
type Person struct { Name string }
func updateName(p *Person, newName string) {
p.Name = newName
}
func main() {
p := Person{Name: "Sam"}
updateName(&p, "Samuel")
fmt.Println(p.Name)
}
An interface defines method signatures; types implement them implicitly.
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
func main() {
var s Speaker = Dog{}
fmt.Println(s.Speak())
}
A type can implement multiple interfaces by having required methods.
package main
import "fmt"
type Reader interface { Read() string }
type Writer interface { Write(string) }
type File struct { data string }
func (f *File) Read() string { return f.data }
func (f *File) Write(s string) { f.data = s }
func main() {
f := &File{}
var r Reader = f
var w Writer = f
w.Write("Hello")
fmt.Println(r.Read())
}
The empty interface interface{}
can hold values of any type.
package main
import "fmt"
func printValue(v interface{}) {
fmt.Println(v)
}
func main() {
printValue(42)
printValue("hello")
}
Extract the dynamic type from an interface value using type assertion.
package main
import "fmt"
func main() {
var i interface{} = "test"
s, ok := i.(string)
if ok {
fmt.Println("String value:", s)
} else {
fmt.Println("Not a string")
}
}
Switch on the dynamic type of an interface value.
package main
import "fmt"
func describe(i interface{}) {
switch v := i.(type) {
case int:
fmt.Println("Integer:", v)
case string:
fmt.Println("String:", v)
default:
fmt.Println("Other type")
}
}
func main() {
describe(10)
describe("hello")
describe(3.14)
}
Methods are functions with a receiver argument.
package main
import "fmt"
type Circle struct { radius float64 }
func (c Circle) Area() float64 {
return 3.14 * c.radius * c.radius
}
func main() {
c := Circle{5}
fmt.Println("Area:", c.Area())
}
Methods can have pointer receivers to modify the struct.
package main
import "fmt"
type Counter struct { count int }
func (c *Counter) Increment() {
c.count++
}
func main() {
c := Counter{}
c.Increment()
fmt.Println(c.count)
}
You can assign a method to a variable and call it later.
package main
import "fmt"
type Person struct { Name string }
func (p Person) Greet() {
fmt.Println("Hi,", p.Name)
}
func main() {
p := Person{"Anna"}
greetFunc := p.Greet
greetFunc()
}
Call a method by referencing the type explicitly.
package main
import "fmt"
type Person struct { Name string }
func (p Person) Greet() {
fmt.Println("Hello", p.Name)
}
func main() {
greet := Person.Greet
p := Person{"John"}
greet(p)
}
Struct embedding allows composition and promotes fields/methods.
package main
import "fmt"
type Animal struct { Name string }
func (a Animal) Speak() {
fmt.Println(a.Name, "makes a sound")
}
type Dog struct {
Animal
}
func main() {
d := Dog{Animal{Name: "Buddy"}}
d.Speak() // Dog inherits Speak method
}
You can override embedded methods by defining a method with the same name.
package main
import "fmt"
type Animal struct{}
func (a Animal) Speak() {
fmt.Println("Animal sound")
}
type Cat struct { Animal }
func (c Cat) Speak() {
fmt.Println("Meow")
}
func main() {
c := Cat{}
c.Speak() // Calls Cat's Speak, overrides Animal
}
Embedded structs can implement interfaces.
package main
import "fmt"
type Speaker interface { Speak() }
type Animal struct{}
func (a Animal) Speak() {
fmt.Println("Animal speaks")
}
type Bird struct { Animal }
func main() {
var s Speaker = Bird{}
s.Speak() // Calls embedded Animal's Speak
}
Only methods with matching receivers satisfy interfaces.
package main
import "fmt"
type Printer interface { Print() }
type Document struct{}
func (d *Document) Print() {
fmt.Println("Printing document")
}
func main() {
var p Printer
d := Document{}
// p = d // ERROR: Document value does not implement Printer
p = &d // OK: *Document implements Printer
p.Print()
}
Interfaces can embed other interfaces to compose behavior.
package main
import "fmt"
type Reader interface { Read() string }
type Writer interface { Write(string) }
type ReadWriter interface {
Reader
Writer
}
type File struct { data string }
func (f *File) Read() string { return f.data }
func (f *File) Write(s string) { f.data = s }
func main() {
var rw ReadWriter = &File{}
rw.Write("Hello")
fmt.Println(rw.Read())
}
Methods add behavior to types; embedding promotes composition over inheritance.
Concurrency allows multiple tasks to run independently to improve efficiency.
Goroutines are lightweight threads managed by Go runtime.
package main
import ("fmt"; "time")
func sayHello() {
fmt.Println("Hello from goroutine")
}
func main() {
go sayHello() // starts goroutine
time.Sleep(time.Second) // wait for goroutine
}
Channels allow communication and synchronization between goroutines.
package main
import "fmt"
func main() {
messages := make(chan string)
go func() { messages <- "ping" }()
msg := <-messages
fmt.Println(msg)
}
Buffered channels have capacity and don’t block immediately on send.
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
You can specify channels as send-only or receive-only for safety.
func send(ch chan<- string, msg string) { ch <- msg }
func receive(ch <-chan string) string { return <-ch }
Use select to wait on multiple channel operations.
package main
import "fmt"
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() { ch1 <- "one" }()
go func() { ch2 <- "two" }()
select {
case msg1 := <-ch1:
fmt.Println("Received", msg1)
case msg2 := <-ch2:
fmt.Println("Received", msg2)
}
}
Mutexes synchronize access to shared variables to avoid race conditions.
package main
import ("fmt"; "sync")
func main() {
var mu sync.Mutex
count := 0
mu.Lock()
count++
mu.Unlock()
fmt.Println(count)
}
WaitGroups wait for a collection of goroutines to finish.
package main
import ("fmt"; "sync")
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Goroutine finished")
}()
wg.Wait()
fmt.Println("All done")
}
Unsynchronized access to shared variables causes race conditions.
// To detect: run with go run -race main.go
Goroutines and channels are the core of Go concurrency, enabling safe parallelism.
Handling unexpected issues gracefully during program execution.
Go uses the built-in error interface for errors.
package main
import ("errors"; "fmt")
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
Create your own error types with more context.
package main
import ("fmt")
type MyError struct { msg string }
func (e *MyError) Error() string {
return e.msg
}
func doSomething(flag bool) error {
if !flag {
return &MyError{msg: "something went wrong"}
}
return nil
}
func main() {
err := doSomething(false)
if err != nil {
fmt.Println("Error:", err)
}
}
Use fmt.Errorf
with %w
to wrap errors.
package main
import ("errors"; "fmt")
func readFile() error {
return errors.New("file not found")
}
func process() error {
err := readFile()
if err != nil {
return fmt.Errorf("process failed: %w", err)
}
return nil
}
func main() {
err := process()
if err != nil {
fmt.Println(err)
}
}
Use errors.Is
and errors.As
to inspect wrapped errors.
package main
import ("errors"; "fmt")
var ErrNotFound = errors.New("not found")
func find() error {
return fmt.Errorf("find error: %w", ErrNotFound)
}
func main() {
err := find()
if errors.Is(err, ErrNotFound) {
fmt.Println("Error is not found")
}
}
Use panic to stop execution and recover to handle panics.
package main
import "fmt"
func risky() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("something bad happened")
}
func main() {
risky()
fmt.Println("Program continues")
}
Return errors, don't panic in normal flow; use errors to communicate problems.
Use log package to record errors with timestamps.
package main
import ("log"; "os")
func main() {
f, err := os.Open("nonexistent.txt")
if err != nil {
log.Println("Error opening file:", err)
}
}
Use packages or custom types to handle multiple errors if needed.
Handle errors explicitly and provide meaningful messages.
Packages organize Go code into reusable units.
Create a directory with Go files having the same package name.
// mathutil/mathutil.go
package mathutil
func Add(a, b int) int {
return a + b
}
Use import statement to use other packages.
package main
import (
"fmt"
"yourmodule/mathutil"
)
func main() {
sum := mathutil.Add(2, 3)
fmt.Println("Sum:", sum)
}
Names starting with uppercase are exported and accessible outside the package.
Modules manage dependencies and versioning.
Initialize a module with go mod init module_name
.
Use go get
to add external packages.
Lists module path and dependencies.
module yourmodule
go 1.20
require github.com/pkg/errors v0.9.1
Optionally store dependencies locally with go mod vendor
.
Packages structure code; modules handle dependency management for scalable projects.
Use os.Open
to open an existing file for reading.
package main
import ("fmt"; "os")
func main() {
file, err := os.Open("test.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
fmt.Println("File opened successfully")
}
Use os.Create
to create or truncate a file.
package main
import ("fmt"; "os")
func main() {
file, err := os.Create("newfile.txt")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()
fmt.Println("File created successfully")
}
Use file.WriteString
to write text to a file.
package main
import ("fmt"; "os")
func main() {
file, _ := os.Create("write.txt")
defer file.Close()
n, err := file.WriteString("Hello, Go!")
if err != nil {
fmt.Println("Error writing to file:", err)
return
}
fmt.Printf("%d bytes written\n", n)
}
Use file.Read
or utilities like io.ReadAll
to read file content.
package main
import ("fmt"; "io"; "os")
func main() {
file, _ := os.Open("write.txt")
defer file.Close()
content, _ := io.ReadAll(file)
fmt.Println("File content:", string(content))
}
Always close files to release resources, often with defer file.Close()
.
// Shown in previous examples
Open file with os.OpenFile
using os.O_APPEND
to add content.
package main
import ("fmt"; "os")
func main() {
file, _ := os.OpenFile("write.txt", os.O_APPEND|os.O_WRONLY, 0644)
defer file.Close()
file.WriteString("\nAppended line")
fmt.Println("Appended successfully")
}
Permissions like 0644 specify who can read/write files.
// Seen in os.Create("file.txt") and os.OpenFile(..., 0644)
Use file.Stat()
to get file metadata.
package main
import ("fmt"; "os")
func main() {
file, _ := os.Open("write.txt")
defer file.Close()
info, _ := file.Stat()
fmt.Println("File name:", info.Name())
fmt.Println("Size:", info.Size())
fmt.Println("Mode:", info.Mode())
}
Use os.Remove
to delete a file.
package main
import ("fmt"; "os")
func main() {
err := os.Remove("newfile.txt")
if err != nil {
fmt.Println("Error deleting file:", err)
} else {
fmt.Println("File deleted successfully")
}
}
File handling is essential for data storage and retrieval in Go.
Structs group related data into a single type.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
p := Person{"Alice", 30}
fmt.Println(p.Name, p.Age)
}
Define methods on structs to attach behavior.
func (p Person) Greet() {
fmt.Println("Hello,", p.Name)
}
Use pointers to modify struct data in methods.
func (p *Person) HaveBirthday() {
p.Age++
}
Interfaces define method sets for types.
type Greeter interface {
Greet()
}
Types implement interfaces by having required methods.
func SayHello(g Greeter) {
g.Greet()
}
interface{}
can hold any type (like any
).
var i interface{}
i = 42
i = "hello"
Extract concrete value from interface.
if s, ok := i.(string); ok {
fmt.Println("String:", s)
}
Switch on type of interface value.
switch v := i.(type) {
case int:
fmt.Println("int", v)
case string:
fmt.Println("string", v)
}
Compose interfaces by embedding others.
type Reader interface {
Read(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Write(p []byte) (n int, err error)
}
Structs model data, interfaces define behavior, enabling flexible designs.
Modules are the way Go manages dependencies and versions.
go mod init example.com/myapp
This creates a go.mod
file for your project.
go get github.com/sirupsen/logrus
This adds the package to your module dependencies.
module example.com/myapp
go 1.20
require github.com/sirupsen/logrus v1.8.1
Records checksums of your dependencies for security.
go get -u github.com/sirupsen/logrus
go mod tidy
Use in go.mod
to replace a dependency for local testing.
replace github.com/pkg/errors => ../errors
go mod vendor
Copies dependencies to a vendor
folder.
Go modules simplify managing external packages and ensure reproducible builds.
In Go, errors are values returned from functions to indicate failure.
package main
import "fmt"
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
The built-in `error` interface represents an error.
type error interface {
Error() string
}
Functions can return multiple values, one being an error.
func sayHello(name string) (string, error) {
if name == "" {
return "", fmt.Errorf("name cannot be empty")
}
return "Hello, " + name, nil
}
Check the error returned before proceeding.
msg, err := sayHello("")
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println(msg)
}
Use errors.New()
to create simple errors.
import "errors"
var ErrEmpty = errors.New("empty input")
func greet(name string) error {
if name == "" {
return ErrEmpty
}
fmt.Println("Hi,", name)
return nil
}
Provides formatted error messages.
return fmt.Errorf("user %s not found", username)
Wrap underlying errors using `%w` verb in fmt.Errorf
.
err := fmt.Errorf("operation failed: %w", ioErr)
Used for checking wrapped error types.
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File does not exist")
}
Checks and unwraps specific error types.
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("Path error:", pathErr.Path)
}
Go encourages explicit, simple error handling instead of exceptions.
Concurrency allows multiple tasks to run simultaneously.
go sayHello() // Starts a goroutine
Lightweight threads managed by Go runtime.
func sayHello() { fmt.Println("Hello") }
go sayHello()
Used to wait for a group of goroutines to finish.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Task done")
}()
wg.Wait()
Used to communicate between goroutines.
ch := make(chan string)
go func() { ch <- "hello" }()
msg := <-ch
fmt.Println(msg)
Have capacity and don’t block immediately.
ch := make(chan int, 2)
ch <- 1
ch <- 2
Limit sending or receiving direction.
func send(ch chan<- int) { ch <- 1 }
Waits on multiple channel operations.
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
}
Prevents race conditions using locks.
var mu sync.Mutex
mu.Lock()
// critical section
mu.Unlock()
Occurs when goroutines access shared data.
Use go run -race to detect data races.
Go provides simple and powerful tools for concurrent programming.
Structs group related data together.
type Person struct {
Name string
Age int
}
Create an instance of a struct using field names.
p := Person{Name: "Alice", Age: 25}
Use dot notation to access or modify fields.
fmt.Println(p.Name)
p.Age = 30
Pass structs to functions.
func greet(p Person) {
fmt.Println("Hi", p.Name)
}
Structs without a name, used for quick grouping.
emp := struct { Name string }{"John"}
Store multiple structs in slices.
people := []Person{{"A", 30}, {"B", 22}}
Structs can contain other structs.
type Address struct { City string }
type User struct { Name string; Addr Address }
Structs are comparable if their fields are comparable.
p1 == p2 // returns true or false
Used for JSON or other annotations.
type Product struct { ID int `json:"id"` }
Structs are powerful for modeling real-world entities.
Defines behavior through method signatures.
type Speaker interface { Speak() }
A type implements an interface by defining its methods.
type Person struct {}
func (p Person) Speak() { fmt.Println("Hello") }
Interfaces allow polymorphic behavior.
func saySomething(s Speaker) { s.Speak() }
Represents any type, useful for generic behavior.
var x interface{} = 5
Extracts the real value from an interface.
val := x.(int)
Handles multiple types using switch.
switch v := x.(type) {
case int: fmt.Println("Int", v)
case string: fmt.Println("String", v)
}
Combining multiple interfaces.
type ReaderWriter interface { Reader; Writer }
Interface with nil underlying type is not nil.
var s Speaker
fmt.Println(s == nil) // true
Create your own for modular design.
type Shape interface { Area() float64 }
Interfaces make Go flexible and extensible.
Errors are values returned as the last result of a function.
err := errors.New("something went wrong")
Check and handle errors immediately.
if err != nil { fmt.Println(err) }
Implement the error interface.
type MyError struct {}
func (e MyError) Error() string { return "my error" }
Use fmt.Errorf to add context.
err := fmt.Errorf("read failed: %w", err)
Predefined errors for comparison.
var ErrNotFound = errors.New("not found")
Check specific error types.
errors.Is(err, ErrNotFound)
Handle serious errors using panic/recover.
defer func() {
if r := recover(); r != nil { fmt.Println("Recovered") }
}()
panic("fail")
Use log package to log errors.
log.Println(err)
Use `_` to ignore error (not recommended).
_, _ = strconv.Atoi("123")
Proper error handling is critical in Go programs.
Goroutines are lightweight threads managed by Go.
go fmt.Println("Hello from goroutine")
WaitGroup is used to wait for a group of goroutines to finish.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Working")
}()
wg.Wait()
Channels are used for communication between goroutines.
ch := make(chan int)
go func() { ch <- 42 }()
fmt.Println(<-ch)
Buffered channels allow sending multiple values without immediate receive.
ch := make(chan int, 2)
ch <- 1
ch <- 2
Channels can be restricted to send-only or receive-only.
func send(ch chan<- int) { ch <- 1 }
Waits on multiple channel operations.
select {
case val := <-ch1:
fmt.Println(val)
case val := <-ch2:
fmt.Println(val)
}
Close a channel to signal no more values will be sent.
close(ch)
Receive values until channel is closed.
for v := range ch { fmt.Println(v) }
Avoid situations where goroutines are stuck waiting.
// Must ensure send and receive both happen
Concurrency is powerful but must be handled carefully.
A collection of Go packages with versioning.
go mod init mymodule
Defines the module name and dependencies.
module mymodule
Automatically updated when importing new packages.
import "github.com/some/module"
Verifies integrity of dependencies.
cat go.sum
Removes unused packages.
go mod tidy
Use `replace` directive in go.mod.
replace example.com/old => ../new
Pin dependencies to a specific version.
require github.com/foo/bar v1.2.3
Use `go get` to upgrade libraries.
go get -u github.com/foo/bar
Copy dependencies locally with `go mod vendor`.
go mod vendor
Go modules help manage project dependencies effectively.
Use os.Open to read files.
file, err := os.Open("data.txt")
Read file contents using ioutil or bufio.
data, _ := ioutil.ReadAll(file)
Use os.Create and Write methods.
f, _ := os.Create("output.txt")
f.Write([]byte("Hello"))
Always close files to avoid resource leaks.
defer file.Close()
Check if a file exists using os.Stat.
_, err := os.Stat("file.txt")
Use os.Remove to delete files.
os.Remove("file.txt")
Use os.OpenFile with append flag.
f, _ := os.OpenFile("log.txt", os.O_APPEND|os.O_WRONLY, 0644)
Use os.Chmod to change permissions.
os.Chmod("file.txt", 0644)
Use bufio.Scanner to read line by line.
scanner := bufio.NewScanner(file)
for scanner.Scan() { fmt.Println(scanner.Text()) }
Go provides comprehensive file handling utilities.
Go uses the built-in testing
package for unit testing.
import "testing"
func TestAdd(t *testing.T) {
result := 2 + 2
if result != 4 {
t.Error("Expected 4")
}
}
Test function names must begin with Test
.
func TestGreet(t *testing.T) {
if greet() != "Hello" {
t.Fail()
}
}
Use go test
to run tests in files ending with _test.go
.
// Command line:
go test
Run tests over multiple inputs using a loop.
func TestSum(t *testing.T) {
cases := []struct{ a, b, want int }{{1, 2, 3}, {2, 2, 4}}
for _, c := range cases {
got := c.a + c.b
if got != c.want {
t.Errorf("got %d, want %d", got, c.want)
}
}
}
Measure performance using Benchmark
functions.
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = 2 + 3
}
}
Measure which code paths were tested.
// Run with:
go test -cover
Use TestMain
for setup/teardown logic.
func TestMain(m *testing.M) {
fmt.Println("Setup")
code := m.Run()
fmt.Println("Teardown")
os.Exit(code)
}
Extract common assertions to helper functions.
func assertEqual(t *testing.T, got, want int) {
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
Manually mock behaviors with interfaces and structs.
type DB interface { Get() string }
type mockDB struct{}
func (m mockDB) Get() string { return "mocked" }
Testing is essential for reliable and maintainable Go programs.
Go provides the net/http
package for building web apps.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, Web!")
})
http.ListenAndServe(":8080", nil)
Register functions to respond to different URLs.
http.HandleFunc("/about", aboutHandler)
Read query and body data, write responses.
r.ParseForm()
name := r.FormValue("name")
fmt.Fprint(w, "Hello ", name)
Use FileServer
to serve files from directories.
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
Implement ServeHTTP
for full control.
type Hello struct{}
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello from custom handler")
}
Parse parameters using query strings.
r.URL.Query().Get("id")
Check method types (GET, POST, etc).
if r.Method == http.MethodPost {
// process POST
}
Use json
package to encode/decode data.
json.NewEncoder(w).Encode(data)
Wrap handlers for logging, auth, etc.
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.Path)
next.ServeHTTP(w, r)
})
}
Go makes web development simple and fast with net/http
.
JSON (JavaScript Object Notation) is a lightweight data interchange format, easy for humans to read and write and easy for machines to parse and generate.
Convert Go structs or data types into JSON using json.Marshal
.
import "encoding/json"
type User struct {
Name string
Age int
}
user := User{"Alice", 25}
data, err := json.Marshal(user)
if err != nil {
panic(err)
}
fmt.Println(string(data)) // Output: {"Name":"Alice","Age":25}
Parse JSON data into Go structs using json.Unmarshal
.
var user User
jsonStr := `{"Name":"Bob","Age":30}`
err := json.Unmarshal([]byte(jsonStr), &user)
if err != nil {
panic(err)
}
fmt.Println(user.Name, user.Age) // Output: Bob 30
Decode JSON into generic maps for flexible handling.
var result map[string]interface{}
jsonStr := `{"Name":"Eve","Age":22}`
json.Unmarshal([]byte(jsonStr), &result)
fmt.Println(result["Name"]) // Output: Eve
Customize JSON key names using struct tags.
type User struct {
Name string `json:"username"`
Age int `json:"age"`
}
user := User{"Charlie", 28}
data, _ := json.Marshal(user)
fmt.Println(string(data)) // Output: {"username":"Charlie","age":28}
Skip empty or zero-value fields in JSON using omitempty
tag.
type User struct {
Name string `json:"name,omitempty"`
Country string `json:"country,omitempty"`
}
user := User{Name: "Dana"}
data, _ := json.Marshal(user)
fmt.Println(string(data)) // Output: {"name":"Dana"}
Make JSON output more readable using json.MarshalIndent
.
data, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(data))
/*
Output:
{
"name": "Dana"
}
*/
Process JSON streams efficiently using json.Decoder
and json.Encoder
.
decoder := json.NewDecoder(reader)
var user User
decoder.Decode(&user)
encoder := json.NewEncoder(writer)
encoder.Encode(user)
Always check for errors during JSON operations.
data, err := json.Marshal(user)
if err != nil {
fmt.Println("Error encoding JSON:", err)
}
JSON handling is essential for data interchange in Go applications, offering powerful tools for encoding and decoding structured data.
Open existing files for reading using os.Open
.
file, err := os.Open("example.txt")
if err != nil {
panic(err)
}
defer file.Close()
Create or overwrite files using os.Create
.
file, err := os.Create("output.txt")
if err != nil {
panic(err)
}
defer file.Close()
Write data to files using Write
or WriteString
.
_, err := file.WriteString("Hello, Go!")
if err != nil {
panic(err)
}
Read file contents using io/ioutil.ReadAll
.
data, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
fmt.Println(string(data))
Use bufio.Reader
for efficient line-by-line reading.
reader := bufio.NewReader(file)
line, err := reader.ReadString('\n')
if err != nil {
panic(err)
}
fmt.Println(line)
Get metadata such as size and permissions using os.Stat
.
info, err := os.Stat("example.txt")
if err != nil {
panic(err)
}
fmt.Println("File size:", info.Size())
Always defer closing files to release resources.
defer file.Close()
Check for errors after every file operation to avoid unexpected behavior.
if err != nil {
fmt.Println("File error:", err)
}
Use os.Rename
and os.Remove
to rename or delete files.
err := os.Rename("old.txt", "new.txt")
if err != nil {
panic(err)
}
err = os.Remove("new.txt")
if err != nil {
panic(err)
}
File handling in Go allows for flexible data management by supporting open, read, write, rename, and delete operations.
Use Go SQL drivers to connect to databases like MySQL, PostgreSQL, SQLite.
import _ "github.com/go-sql-driver/mysql"
Connect to database using sql.Open
.
db, err := sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/dbname")
if err != nil { panic(err) }
defer db.Close()
Check connection with db.Ping()
.
if err := db.Ping(); err != nil { panic(err) }
Run SQL commands with db.Exec
.
res, err := db.Exec("INSERT INTO users(name) VALUES(?)", "Alice")
if err != nil { panic(err) }
Fetch rows using db.Query
or db.QueryRow
.
row := db.QueryRow("SELECT name FROM users WHERE id = ?", 1)
var name string
err := row.Scan(&name)
if err != nil { panic(err) }
fmt.Println(name)
Prepare statements to execute multiple times safely.
stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)")
if err != nil { panic(err) }
defer stmt.Close()
_, err = stmt.Exec("Bob")
Execute multiple queries atomically with transactions.
tx, err := db.Begin()
if err != nil { panic(err) }
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = ?", 1)
if err != nil { tx.Rollback(); panic(err) }
tx.Commit()
Always close rows to free resources.
rows, err := db.Query("SELECT * FROM users")
if err != nil { panic(err) }
defer rows.Close()
Check errors after all DB operations.
if err != nil { fmt.Println("DB error:", err) }
Go's database/sql package provides a flexible way to work with databases using drivers, queries, transactions, and prepared statements.
Test functions start with Test
and take *testing.T
.
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Error("Expected 5")
}
}
Run tests with go test
command in terminal.
Test multiple cases in a loop for clean code.
tests := []struct{ a, b, want int }{
{1, 2, 3},
{2, 2, 4},
}
for _, tt := range tests {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("Add(%d,%d) = %d; want %d", tt.a, tt.b, got, tt.want)
}
}
Write benchmarks using func BenchmarkXxx(b *testing.B)
.
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
Check coverage with go test -cover
.
Create helper functions to reduce repetition in tests.
Use interfaces and mocks to isolate tests.
Benchmark multiple cases using table-driven approach.
Use t.Fatal
to stop tests on fatal errors.
Testing in Go is simple and powerful, promoting good practices and robust code.
Build optimized binaries using go build -ldflags="-s -w"
.
go build -ldflags="-s -w" main.go
Compile binaries for different platforms.
GOOS=linux GOARCH=amd64 go build -o app-linux main.go
Configure app settings securely using env vars.
import "os"
port := os.Getenv("PORT")
Create lightweight containers for easy deployment.
FROM golang:1.20-alpine
WORKDIR /app
COPY . .
RUN go build -o myapp .
CMD ["./myapp"]
Use config files with libraries like viper
.
Use structured logging with levels.
log.SetFlags(log.LstdFlags | log.Lshortfile)
Gracefully shutdown on OS signals.
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
fmt.Println("Shutting down")
Use tools like Prometheus for monitoring.
Manage your Go app with tools like systemd or supervisord.
Proper deployment practices ensure your Go app is reliable, efficient, and maintainable.
Never store passwords or keys in code. Use environment variables.
password := os.Getenv("DB_PASSWORD")
Always sanitize and validate inputs to avoid injection.
if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(username) {
return errors.New("Invalid username")
}
Serve your app over TLS to encrypt data.
http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)
Set cookies with Secure and HttpOnly flags.
cookie := &http.Cookie{
Name: "session",
Value: "xyz",
Secure: true,
HttpOnly: true,
}
http.SetCookie(w, cookie)
Use tokens to prevent Cross-Site Request Forgery.
// Example: Generate and validate CSRF token in forms
Prevent SQL injection by using prepared statements.
stmt, _ := db.Prepare("SELECT * FROM users WHERE id = ?")
row := stmt.QueryRow(userID)
Don’t expose internal error details to users.
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", 500)
}
Set timeouts and limits to prevent DoS attacks.
srv := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
Keep your packages up to date to avoid vulnerabilities.
go get -u ./...
Applying security best practices ensures your Go apps remain safe and robust.
Use pprof
to analyze CPU and memory usage.
import _ "net/http/pprof"
go http.ListenAndServe(":6060", nil)
Write benchmarks with testing.B
.
func BenchmarkFib(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(10)
}
}
Release unused memory and close resources.
defer file.Close()
Use strings.Builder
to concatenate strings efficiently.
var b strings.Builder
b.WriteString("Hello")
b.WriteString(" World")
result := b.String()
Don’t create excessive goroutines to avoid overhead.
go func() { /* work */ }()
Reuse frequently allocated objects to reduce GC pressure.
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
Design concurrent code to reduce locks.
Choose the right data structure for your problem.
Small functions are often inlined automatically for speed.
Profiling and optimization improve your Go app's speed and resource usage.
Preparing your app to support multiple languages.
Use this package for language and locale support.
import "golang.org/x/text/language"
Detect user language preference via HTTP headers.
lang := r.Header.Get("Accept-Language")
Store translations in JSON or YAML files.
Load translation files at startup.
Format messages for a given language.
p := message.NewPrinter(language.English)
p.Printf("Hello, %s!", "John")
Inject localized strings into templates.
Handle plurals correctly per language rules.
Format dates and times per locale.
Internationalization makes your Go apps usable worldwide.