Go Notebook

Follow my hands-on Go learning journey through LeetCode solutions and real-world comparisons with JavaScript. Discover how Go's concurrency and memory efficiency solve problems where JS hits limits.

πŸš€ Intro

i’m diving into Go to add a new tool to my coding arsenal, aiming to harness its speed and efficiency for those times when JavaScript just can’t keep up with massive datasets. By tackling LeetCode problems and comparing solutions with JS, i’m not just learning a new languageβ€”i’m preparing for real-world scenarios where Go’s lightweight nature and concurrency shine. Whether it’s crunching big data or optimising performance, having Go as an option means i’m ready for whatever coding challenges come my way. πŸš€

CriteriaGo 🦫JavaScript 🟨
Speed5-10x faster for CPU tasksSlower, but adequate for most web apps
Memory Efficiency~1/3 JS usage, manual controlAutomatic, hits ~4GB limit
Best For- Big data (>1GB)
- High concurrency
- CLI tools
- Web APIs
- UI apps
- Prototyping
ConcurrencyGoroutines (cheap, 1MB each)Worker threads (heavy, ~10MB)
EcosystemGrowing stdlib, fewer libsMassive NPM ecosystem
Learning CurveStrict types, explicit error handlingFamiliar syntax, flexible types
When to UseData pipelines, microservicesFull-stack apps, quick scripts

Data Pipeline = A series of automated steps that collect, process, and move data from one system to another (e.g., CSV files β†’ cleaned data β†’ database).

Decision Checklist:

  1. Processing >1GB data? β†’ Go
  2. Need precise memory control? β†’ Go

Precise Memory Control = In Go, you manually manage memory allocation (e.g., buffer := make([]byte, 0, 1024) pre-allocates 1KB), preventing unexpected memory spikes common in JS.

  1. Building UI/API? β†’ JS
  2. Team knows JS better? β†’ JS
  3. Need single binary? β†’ Go (A self-contained executable with all dependencies packed in)

πŸ“Œ Core Concepts

ConceptJavaScript ExampleGo Example & Notes
Variableslet x = 10;x := 10 (type inferred)
var y int = 20 (explicit)
Constantsconst PI = 3.14;const PI = 3.14 (untyped)
const (A=1; B=2) (grouped)
Functionsfunction add(a, b) { ... }func add(a int, b int) int { ... }
Multiple returns: (int, error)
Data TypesDynamic typingStatic typing:
var s string = "text"
n := 3.14 (float64)
Collectionsconst arr = [1,2];
const obj = {key:1};
arr := []int{1,2} (slice)
m := map[string]int{"key":1}
type S struct{Key int}
Zero Valuesundefined, null0, "", false, nil (explicit defaults)
Error Handlingtry/catchif err != nil { ... }
val, err := someFunc()
ConcurrencyPromise, async/awaitgo func() { ... } (goroutines)
ch := make(chan int)
MemoryAutomatic GCManual control:
buf := make([]byte, 0, 1024) (pre-alloc)
Buildnode script.jsgo build β†’ single binary
No external runtime needed

Code Examples

1// JavaScript
2let name = "John";
3let age = 30;
4let scores = [90, 85, 95];
1// Go
2name := "John"          // string
3var age int = 30        // explicit type
4scores := []int{90, 85, 95}  // slice

Common Questions

  1. What is := in Go? β†’ Short variable declaration (creates and assigns value at once).
  2. Can you use let in Go? β†’ No, use var or := instead.
  3. Can you reassign a variable in Go? β†’ Yes, but you can’t redeclare the same variable name.
  4. Can := be used to reassign new value to a variable? β†’ Yes, but you can’t redeclare the same variable name.
  5. Do I need to specify types in Go? β†’ No, but Go is statically typed. Use var x int = 10 or x := 10.
  6. What is hoisting? β†’ In JavaScript, var declarations are moved to the top of the scope before code execution. Go does not have hoisting, so variables must be declared before use.
  7. What is * in Go? β†’ It is a pointer. It is used to store the address of a variable.
    1. When should I use it? β†’ When you want to pass a variable by reference. (Need more explanation)

βš™οΈ Functions

FeatureJavaScriptGo
Basic Functionfunction add(a, b) { ... }func add(a int, b int) int { ... }
Return ValuesSingle valueMultiple values: return val, err
Anonymous Func() => {}func() { ... }
Closure Supportβœ…βœ…

Code Examples

1// JavaScript
2function sum(...nums) {
3    return nums.reduce((a, b) => a + b, 0);
4}
 1// Go
 2func sum(nums ...int) int {
 3    total := 0
 4    for _, num := range nums {
 5        total += num
 6    }
 7    return total
 8}
 9
10// Multiple return values
11func divide(a, b float64) (float64, error) {
12    if b == 0 {
13        return 0, errors.New("division by zero")
14    }
15    return a/b, nil
16}

Common Questions

  1. Can functions return multiple values? β†’ Yes, return val1, val2.
  2. How do I handle optional parameters? β†’ Use variadic functions: func sum(nums ...int) int.
  3. Can we pass an object-like structure to a function like JS? β†’ Use structs in Go.

πŸ›  Data Structures

Arrays/Slices vs JS Arrays

OperationJavaScriptGo
Createconst arr = [1,2,3];slice := []int{1,2,3}
Appendarr.push(4)slice = append(slice, 4)
Lengtharr.lengthlen(slice)
CapacityDynamiccap(slice)

Code Examples

1// JavaScript
2const numbers = [1, 2, 3];
3numbers.push(4);
1// Go
2numbers := []int{1, 2, 3}
3numbers = append(numbers, 4)
4
5// Pre-allocate capacity
6buffer := make([]int, 0, 10)  // length 0, capacity 10 - A slice with a pre-allocated capacity

Key Differences

AspectJavaScriptGo
Memory ManagementAutomaticManual control
Array Growthpush() handles everythingappend() may need reallocations
PerformanceOptimised for convenienceOptimised for control
Best PracticeJust use push()Pre-allocate when size is known

(WIP) Practical Example: Collecting 1M Numbers

JavaScript (Automatic)
1const numbers = [];
2for (let i = 0; i < 1000000; i++) {
3    numbers.push(i); // Engine handles everything
4}
Go (Manual Control)
1// Best Practice: Pre-allocate when size is known
2numbers := make([]int, 0, 1000000) // Single allocation
3for i := 0; i < 1000000; i++ {
4    numbers = append(numbers, i) // No reallocations
5}

When to Pre-allocate in Go

  1. Known Size - When you know the maximum elements
  2. Performance Critical - High-frequency operations
  3. Large Data - Processing big datasets
  4. Memory Efficiency - Constrained environments

When Not to Pre-allocate

  1. Small Data - Few elements, minimal impact
  2. Unknown Size - Can’t predict capacity
  3. Prototyping - Focus on functionality first

Real-world Pattern

 1// Typical Go approach
 2func processItems(items []Item) {
 3    // Pre-allocate result slice
 4    results := make([]Result, 0, len(items))
 5
 6    for _, item := range items {
 7        result := process(item)
 8        results = append(results, result)
 9    }
10
11    return results
12}

Key Takeaways

  1. JavaScript - Just use push(), engine handles memory
  2. Go - Pre-allocate with make() when performance matters
  3. Trade-off - Go gives control, JavaScript offers simplicity
  4. Best Practice - In Go, pre-allocate for known sizes, otherwise let append handle it

Common Questions

  1. What is the difference between arrays and slices? β†’ Arrays have fixed sizes; slices are dynamic.
  2. How do I initialize an empty slice? β†’ var arr []int or arr := make([]int, 0).

Objects vs Structs

FeatureJavaScriptGo
Definitionconst obj = { ... };type Struct struct { ... }
Methodsobj.method = () => {}func (s Struct) Method() {}
ImmutabilityObject.freeze()Unexported fields

Code Examples

1// JavaScript
2const user = {
3    id: 1,
4    name: "John",
5    greet() {
6        console.log(`Hello ${this.name}`);
7    }
8};
 1// Go
 2type User struct {
 3    ID   int
 4    Name string
 5}
 6
 7func (u User) Greet() {
 8    fmt.Printf("Hello %s", u.Name)
 9}
10
11john := User{ID: 1, Name: "John"}
12john.Greet()

JS Maps vs Go Maps

FeatureJavaScriptGo
Create a Mapconst myMap = new Map();myMap := make(map[int]int)
Set a ValuemyMap.set(1, 'a');myMap[1] = "a"
Get a ValuemyMap.get(1)val := myMap[1]
Check ExistencemyMap.has(1)val, exists := myMap[1]
 1// Instead of JS objects
 2type User struct {
 3    ID        int    `json:"id"`
 4    Name      string `json:"name"`
 5    Age       int    `json:"age"`
 6    CreatedAt time.Time
 7}
 8
 9// Creating instances
10user := User{
11    ID:   1,
12    Name: "John",
13    Age:  30,
14}

Common Questions

  1. How do I check if a key exists in Go? β†’ val, exists := myMap[key] (returns value & boolean in _, _ format).
  2. Does Go have an equivalent of JavaScript objects? β†’ Use structs instead of maps for structured data.

🚨 Error Handling (WIP)

PatternJavaScriptGo
Basic Handlingtry { ... } catch(err) {}if err != nil { return err }
Error Creationthrow new Error("error ...")return errors.New("error ...")
Custom Errorsclass CustomErrortype CustomError struct{...}

Code Examples

1// JavaScript
2try {
3    riskyOperation();
4} catch (err) {
5    console.error(err);
6}
 1// Go
 2func main() {
 3    err := riskyOperation()
 4    if err != nil {
 5        log.Fatal(err)
 6    }
 7}
 8
 9// Custom error
10type TimeoutError struct {
11    Operation string
12    Duration  time.Duration
13}
14
15func (e *TimeoutError) Error() string {
16    return fmt.Sprintf("%s timed out after %v", e.Operation, e.Duration)
17}

Common Questions

  1. Does Go have exceptions? β†’ No, Go uses if err != nil.
    • How to handle errors like a pro? Is there a cleaner way to handle errors?
  2. How do I create a custom error? β†’ errors.New("message").

πŸ”„ Concurrency Patterns (WIP)

ConceptJavaScriptGo
Async Operationasync/awaitGoroutines
CommunicationPromisesChannels
ParallelismWorker ThreadsGoroutines + CPU Cores

Code Examples

1// JavaScript
2async function fetchData() {
3    const res = await fetch(url);
4    return res.json();
5}
 1// Go
 2func fetchData(url string, ch chan<- Result) {
 3    res, err := http.Get(url)
 4    if err != nil {
 5        ch <- Result{Error: err}
 6        return
 7    }
 8    ch <- parseResponse(res)
 9}
10
11func main() {
12    ch := make(chan Result)
13    go fetchData("https://api.example.com", ch)
14    result := <-ch
15}

πŸ† Best Practices (WIP)

 1// 1. Error Wrapping
 2func processFile(path string) error {
 3    f, err := os.Open(path)
 4    if err != nil {
 5        return fmt.Errorf("processFile: %w", err)
 6    }
 7    defer f.Close()
 8    // ... file processing ...
 9}
10
11// 2. Interface Design
12type Writer interface {
13    Write([]byte) (int, error)
14}
15
16// 3. Memory Management
17func processLargeData() {
18    data := make([]byte, 0, 1e6)  // Pre-allocate
19    // ... process data ...
20}

πŸ“š Python vs Go: Choosing the Right Tool

CriteriaPython 🐍Go 🦫
Code Readabilityβœ… Super concise, easy to read❌ More verbose (need structs, types)
Library Supportβœ… Tons of built-in functions (heapq, collections, bisect)❌ Need to manually implement data structures like heaps, sets
Typingβœ… Dynamic (faster coding)❌ Static (safer but more rigid)
Execution Speed❌ Slower for large inputs (interpreted)βœ… Faster (compiled, better memory efficiency)
Memory Usage❌ Higher (Garbage Collected, dynamic objects)βœ… Lower (Manual memory control, efficient types)
Concurrency❌ Python’s GIL limits true parallel executionβœ… Go routines are lightweight and great for concurrency
Industry Use in SWEβœ… Used in ML, Web Dev, and Scripting (most interviews support Python)βœ… Used in backend, cloud, and infra (great for system-heavy roles)
LeetCode Community & Discussionβœ… More Python solutions, easier to find help❌ Fewer Go solutions, harder to debug

πŸš€ LeetCode Roadmap for Go

Phase 1: Learning Go Basics (1-2 Weeks)

Goal: Get comfortable with Go syntax and basic concepts.

  • βœ… Variables, Constants, and Types (:=, var, const)
  • βœ… Functions (func, multiple return values)
  • βœ… Data Structures (map, slice, struct)
  • βœ… Pointers (*, &)
  • βœ… Loops (for, range)
  • βœ… Error Handling (if err != nil)

Phase 2: Easy LeetCode Questions (2-3 Weeks)

Goal: Manage to solve easy questions in Go without much help.

  • βœ… Arrays & Strings
  • βœ… HashMaps & Sets
  • βœ… Linked Lists

Phase 3: Medium LeetCode Questions (3-5 Weeks)

Goal: Manage to solve medium questions in Go without much help.

  • βœ… Sorting & Searching
  • βœ… Recursion & Backtracking
  • βœ… Graphs & Trees

Phase 4: Hard LeetCode Questions (Ongoing)

Goal: Manage to solve hard questions in Go without much help.

  • βœ… Graphs & Trees

Phase 5: Advanced Go Concepts

Goal: Get comfortable with advanced Go concepts.

  • βœ… Goroutines & Concurrency
  • βœ… Channels & Select Statements
  • βœ… Writing Go Unit Tests (testing package)

🧩 LeetCode Practices

35. Search Insert Position

Key Insight

Binary search implementation where left pointer naturally converges to insertion point when target not found.

Go Implementation

 1func searchInsert(nums []int, target int) int {
 2    left, right := 0, len(nums)-1
 3
 4    for left <= right {
 5        mid := left + (right-left)/2 // Prevent overflow
 6        switch {
 7        case nums[mid] == target:
 8            return mid
 9        case nums[mid] < target:
10            left = mid + 1
11        default:
12            right = mid - 1
13        }
14    }
15    return left // Insert position when not found
16}

Go-Specific Learnings

  1. Loop Structure for left <= right replaces JS while loop
  2. Mid Calculation left + (right-left)/2 prevents integer overflow
  3. Return Value left always points to insertion index post-loop

Edge Cases

  • Empty array β†’ returns 0
  • Target smaller than all elements β†’ 0
  • Target larger than all elements β†’ len(nums)

Complexity

  • Time: $O(log n)$
  • Space: $O(1)$

Why This Works

  • Binary search reduces problem space by half each iteration
  • Final left position reflects where target would maintain sorted order

118. Pascal’s Triangle

Key Insight

Pre-allocate slices and manually initialize endpoints since Go arrays lack built-in fill methods.

Go Implementation

 1func generate(numRows int) [][]int {
 2    res := make([][]int, numRows) // Outer slice
 3    for i := range res {
 4        res[i] = make([]int, i+1) // Inner slice
 5        res[i][0], res[i][i] = 1, 1 // Endpoints
 6        for j := 1; j < i; j++ {
 7            res[i][j] = res[i-1][j-1] + res[i-1][j] // Sum parents
 8        }
 9    }
10    return res
11}

Go-Specific Learnings

  1. Slice Initialisation
    • make([]int, n) creates zero-initialised slices
    • Must manually set first/last elements to 1
  2. Nested Slices make([][]int, rows) β†’ Each inner slice needs separate allocation
  3. Index Boundaries j runs from 1 to i-1 to avoid overwriting endpoints

Memory Management

ApproachGoJavaScript
Creationmake([]int, 0, capacity)new Array(n).fill(0)
FillManual initialisationBuilt-in fill() method
MatrixNested make() callsArray of arrays

Example for make([]int, 0, capacity):

1// Without pre-allocation
2var slow []int          // Length 0, Capacity 0
3slow = append(slow, 1) // New allocation needed
4
5// With pre-allocation
6fast := make([]int, 0, 1000)
7for i := 0; i < 1000; i++ {
8    fast = append(fast, i) // No reallocations
9}

Edge Cases

  • numRows = 0 β†’ Return empty slice
  • numRows = 1 β†’ [[1]]

Complexity

  • Time: $O(nΒ²)$ - Each element computed once
  • Space: $O(nΒ²)$ - Stores entire triangle

Why This Works

  • Pre-allocation avoids slice growth overhead
  • Mathematical properties of Pascal’s Triangle enable efficient computation

2364. Count Number of Bad Pairs

Key Insight

Transform j - i != nums[j] - nums[i] β†’ i - nums[i] != j - nums[j]. Track i - nums[i] counts using map.

Go Implementation

 1func countBadPairs(nums []int) int64 {
 2    total := int64(len(nums)*(len(nums)-1)/2)
 3    good := int64(0)
 4    cache := make(map[int]int) // Key: i - nums[i]
 5
 6    for i, num := range nums {
 7        key := i - num
 8        good += int64(cache[key]) // Count existing matches
 9        cache[key]++ // Update after counting
10    }
11
12    return total - good
13}

Go-Specific Learnings

  1. Map Initialisation cache := make(map[int]int)

    • Zero-value initialised (no nil issues)
    • Auto-expands as needed
  2. Unused Variables Go compiler errors if:

    1for i, num := range nums { // Must use both i and num
    2    // If not, use _: for _, num := range nums
    3}
    
  3. Efficient Counting

    • cache[key] returns 0 if missing (no panic)
    • cache[key]++ handles insertion/update

Complexity

  • Time: $O(n)$ - Single pass with map lookups
  • Space: $O(n)$ - Worst-case unique keys

Why This Works

  • Each key collision represents a good pair
  • Total pairs = $n * (n-1)/2$
  • Bad pairs = Total - Good

219. Contains Duplicate II

Key Insight

Track last seen indices using a hashmap to check for duplicates within k distance in $O(n)$ time.

Go Implementation

 1func containsNearbyDuplicate(nums []int, k int) bool {
 2	// N = 6
 3	// k = 2
 4	// [0,1,2,3,4,5]
 5	// [1,2,3,1,2,3]
 6	// e.g. 3
 7	// => 3 - 2 = 1
 8
 9	numToIndexMap := make(map[int]int)
10
11	for i := 0; i < len(nums); i++ {
12		if existedIndex, exists := numToIndexMap[nums[i]]; exists {
13
14			// if existedIndex >= (i-k) || (i+k) <= existedIndex {
15			if (i - existedIndex) <= k {
16				return true
17			}
18		}
19
20		numToIndexMap[nums[i]] = i
21	}
22
23	return false
24}

Go-Specific Learnings

  1. Map Initialisation make(map[int]int) creates an empty hashmap that auto-expands

  2. Index Management

    • Store current index on each iteration
    • Check i-j (not absolute value since i > j)
  3. Efficiency

    • Single pass $O(n)$ time
    • Worst-case $O(n)$ space for all unique elements

Edge Cases

  • k=0 β†’ Always false (indices must differ)
  • All elements unique β†’ false
  • Duplicates exactly k apart β†’ true

Complexity

  • Time: $O(n)$
  • Space: $O(n)$

228. Summary Ranges

Key Insight

Track range starts and detect gaps in a single pass, leveraging Go’s slice efficiency for $O(n)$ time.

Go Implementation

 1func summaryRanges(nums []int) []string {
 2	N := len(nums)
 3	ranges := []string{}
 4
 5	if N == 0 {
 6		return ranges
 7	}
 8
 9	startIndex := 0
10	for i := 1; i < N; i++ {
11		if nums[i-1] != (nums[i] - 1) {
12			var tempStr string
13
14			if startIndex == i-1 {
15				tempStr = fmt.Sprintf("%d", nums[startIndex])
16			} else {
17				tempStr = fmt.Sprintf("%d->%d", nums[startIndex], nums[i-1])
18			}
19			ranges = append(ranges, tempStr)
20			startIndex = i
21		}
22	}
23
24	var tempStr string
25	if startIndex == (N - 1) {
26		tempStr = fmt.Sprintf("%d", nums[startIndex])
27	} else {
28		tempStr = fmt.Sprintf("%d->%d", nums[startIndex], nums[N-1])
29	}
30	ranges = append(ranges, tempStr)
31
32	return ranges
33}

Go-Specific Learnings

  1. Slice Management

    • Pre-allocate with var ranges []string
    • Use append for dynamic growth
  2. Helper Functions Extract formatting logic for cleaner code

  3. Edge Cases

    • Empty input β†’ return empty slice
    • Single element β†’ ["5"]

Complexity

  • Time: $O(n)$ - Single pass through array
  • Space: $O(n)$ - Worst case stores n/2 ranges

Why This Works

  • Detects non-consecutive numbers (nums[i] != nums[i-1]+1)
  • Builds ranges incrementally
  • Handles final range outside loop

🚧 WIP …

comments powered by Disqus