Map

  • A map is a built-in data structure used to store key-value pairs.
  • Each key is unique and maps to a value.
  • Maps are unordered - iteration order is not guaranteed
  • Maps are reference types - passed by reference to functions
  • Not safe for concurrent use - use sync.Map or mutex for concurrent access
// Decleration
var m map[string]int // A declared map is `nil` until initialized. 
/*
- `string` → key type  
- `int` → value type  
*/
 
 
// Initialization
m := make(map[string]int)  // using make
 
m := map[string]int{  // using map literal 
	"apple": 10,  
	"banana": 20,  
}  
 
 
// Adding & Updating values
m["apple"] = 30  
m["orange"] = 15    
// If the key exists → value is updated.  
// If the key does not exist → a new entry is created.  
 
 
// Accessing values
value := m["apple"]
value, exists := m["apple"] // if value doesn't exist value=zero value & exists=false
 
 
// Deleting from map
delete(m, "apple")
 
 
// Iterate over map
for key, value := range m {  
	fmt.Println(key, value)  
}  
 
 
// Length of map - count of key value pairs exising
len(m)

Pointers

  • A pointer is a variable that stores the memory address of another variable.
  • Pointers allow functions to modify the original value instead of working on a copy.
  • Go does not support pointer arithmetic.
  • A pointer stores a memory address.
  • & → address operator.
  • * → dereference operator.
  • slices & maps are already pointers
// Decleration
var p *int
// `p` → pointer variable  
// `*int` → pointer to an integer
 
 
x := 10  
p := &x  
// `x` → value  
// `&x` → memory address of `x`  
// `p` now stores the address of `x`  
 
 
// Dereferencing a pointer
x := 10  
p := &x  
fmt.Println(*p) // 10
 
 
// Modifying value using pointer
x := 10  
p := &x
*p = 20 // x = 20 now 
 
 
// Passing pointer to a function (pass by value, the catch is value is a pointer to a variable.)
func update(n *int) {  
	*n = 50  
}  
x := 10  
update(&x) // x updated to 50
 
 
// Creating a pointer using new
p := new(int) // Default value is 0 for int
 
 
// Pointer to a struct (go automatically dereferences struct pointer)
type Person struct {  
	name string  
	age int  
}
p := &Person{name: "John", age: 25}  
// Access using p.name & p.age, no need for (*p).name

Method

  • Functions with special reciever
  • A method is a function associated with a specific type.
  • It allows you to define behavior for a type.
func (receiver Type) MethodName(parameters) returnType {  
// method body  
}
 
 
type Rectangle struct {  
	width int  
	height int  
}
func (r Rectangle) Area() int {  
	return r.width * r.height  
}
rect := Rectangle{width: 10, height: 5}  
area := rect.Area()
  • Value & Pointer receiver
  • When to use pointer receiver:
    • Need to modify the receiver
    • Receiver is a large struct (avoid copying)
    • Consistency (if some methods use pointer receivers, all should)
func (r Rectangle) Area() int {  // Value reciever, The method receives a **copy of the value**.
	return r.width * r.height  
}
 
func (r *Rectangle) Scale(factor int) {  // Pointer reciever, recieves a pointer to the type
	r.width = r.width * factor  
	r.height = r.height * factor  
}

Interfaces

  • An interface defines a set of method signatures.
  • Any type that implements those methods automatically satisfies the interface.
  • Go uses implicit implementation (no implements keyword).
  • interfaces often end with er, like Reader, Writer, Stringer etc.
  • Since Go 1.18, any is an alias for interface{}
  • An interface value contains two things:
    • Concrete value
    • Type of the value
// Declaring a interface
type Shape interface {  
	Area() float64  
}
type Shape2 interface {  
	Area() float64  
	Perimeter() float64  
}
// Any type that defines `Area() float64` satisfies `Shape`.
 
// Implementing a interface
type Rectangle struct {  
	width float64  
	height float64  
}
func (r Rectangle) Area() float64 {  
	return r.width * r.height  
}
 
// Using an Interface
func PrintArea(s Shape) {  
	fmt.Println(s.Area())  
}  
  
rect := Rectangle{10, 5}  
PrintArea(rect)
  • Empty Interface - The empty interface has no methods. Can hold any value interface{}
var v interface{}  
v = 10  
v = "hello"
  • Type assertions - Used to extract the underlying value from an interface. if assertions fails then they cause panic(full termination of program)
// Type assertions
var v interface{}  
v = 10
v := i.(int)
v, ok := i.(int)
 
// Type Switches, Switch for type assertions
switch v := i.(type) {  
	case int:  
		fmt.Println("int", v)  
	case string:  
		fmt.Println("string", v)  
	default:  
		fmt.Println("unknown type")  
}

Error Interface

  • Error interface
type error interface {
	Error() string
}
 
// usage
func DoSomething() (int, error) { 
	// ... 
}
 
// create a new error object
var ErrNotFound = errors.New("resource was not found")
// nil for no error
 
// Create custom error
type MyCustomError struct {
  message string
  details string
}
func (e *MyCustomError) Error() string {
  return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
  // ...
  return &MyCustomError{
    message: "...",
    details: "...",
  }
}
 

”stringer” interface

  • Stringer is an interface for defining the string format of values. The interface consists of a single String method:
type Stringer interface {
    String() string
}

Types that want to implement this interface must have a String() method that returns a human-friendly string representation of the type.

  • similar to overriding the toString() method in Java.