๐Ÿ“– bytebook

A living notebook of everything I learn in Golang, Rust, and other languages โ€” slowly being forged into a full-blown mdBook.


๐Ÿš€ What's Inside?

Organized by language, concept, and chaos-resistance.

๐ŸŸฆ Golang

๐Ÿ—„๏ธ SQL

  • ๐Ÿ“ View SQL Cheatsheets, Flashcards, and Practice
  • Relational theory, query mastery, and interview prep

๐Ÿงช Templates

  • ๐Ÿ“ Common code snippets and setup scripts
  • Shell templates, project boilerplates, and productivity hacks

๐Ÿฆ€ Rust (coming soon)

  • Ownership, lifetimes, async, and zero-cost abstractions

๐Ÿ Python (coming soon)

  • Mostly for scripting tools & small hacks

๐Ÿ”ฎ Vision

Iโ€™ll keep updating this with bite-sized lessons, code experiments, and deeper dives.
Eventually, this will become a full mdBook, so anyone (including future me) can browse and learn.


๐Ÿ›  How To Use

  • Clone the repo
  • Pick your poison (golang/, rust/, etc.)
  • Read the markdown, run the code, level up.

๐Ÿ“˜ Planned Features

  • Interactive playground links
  • Auto-generated SUMMARY.md for mdBook
  • E-book/PDF export support

๐Ÿค Contributions

Right now, it's my personal logbook, but in the future I might open it for collabs!


โšก Built With

  • Markdown
  • mdBook (WIP)
  • Love for clean, powerful code

๐Ÿน Golang Playground

Welcome to my Golang knowledge hub โ€” a growing collection of experiments, course notes, and practical learnings as I dive deeper into Go ๐Ÿš€


๐Ÿ“‚ Structure (will be updated in future)

golang/ 
    โ”œโ”€โ”€ experiment/
    โ””โ”€โ”€ go-with-habib-class-notes/

๐Ÿ”ฌ experiment/

A sandbox for all my Go experiments. From testing features to quick problem-solving attempts, this folder is all about learning by doing.

๐Ÿ“˜ go-with-habib-class-notes/

Detailed notes and example code from the "Go with Habib" course. Organized by class, with code and key takeaways.


๐Ÿ“Œ What's Next?

  • Add more Go mini-projects and tools under experiment/
  • Continue documenting the Habib course in markdown
  • Add README.md for each class and experiment
  • Eventually turn this entire golang/ folder into part of the bytebook mdBook

This is just the beginning. Watch this space as my Go journey evolves! ๐Ÿ˜ค

Go With Habib Class Notes

Welcome to the class notes from the "Go With Habib" YouTube course series on Golang! ๐Ÿš€

This section contains carefully structured notes organized class-by-class to help you:

  • Understand core Golang concepts,
  • Follow real examples discussed in the videos,
  • Revise faster before interviews or projects.

Each class builds on top of the previous ones, taking you from basics to advanced topics with real-world insights. ๐ŸŒŽ

โœ๏ธ All notes are made with love, clarity, and future you in mind. Stay consistent and code like a beast! ๐Ÿ’ช


Let's Go! ๐ŸŽ๏ธ๐Ÿ’จ

Just a test

Golang Basics ๐Ÿš€

Welcome to the Go world! This doc will help you get started with installing Go and understanding the basics of the language's structure.


๐Ÿ”ง Installing Go

๐Ÿ— Linux (Debian/Ubuntu)

sudo apt update
sudo apt install golang-go

๐Ÿงฑ Arch-based Linux (like Manjaro, EndeavourOS, etc.)

sudo pacman -S go

๐Ÿ macOS (using Homebrew)

brew install go

๐ŸชŸ Windows

  1. Download the installer from: https://go.dev/dl
  2. Run the installer and follow the prompts.
  3. Restart your terminal and verify with:
go version

๐Ÿ” Environment Setup

Make sure GOPATH and GOROOT are correctly configured.

For most setups, adding this to your .bashrc or .zshrc helps:

export PATH=$PATH:/usr/local/go/bin

For Arch-based systems installed via pacman, this is usually set correctly by default.


๐Ÿ“ Hello, World! Example

package main

import "fmt"

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

๐Ÿ” Explaining the Syntax

package main

This tells Go that this is an executable program (not a shared library). When a Go program is compiled, it looks for package main and executes the main() function.

import

Used to bring in standard or external packages. For example:

import "fmt" // "fmt" provides formatted I/O

You can import multiple packages like this:

import (
	"fmt"
	"math"
)

func main()

This is the entry point of the program. Go will automatically look for the main function and execute it.


๐Ÿ“Œ Other Common Basics

Variables

var x int = 5
y := 10 // short declaration

Constants

const pi = 3.14

Functions

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

If / Else

if x > y {
	fmt.Println("x is bigger")
} else {
	fmt.Println("y is bigger")
}

Switch

switch x {
case 1:
	fmt.Println("One")
case 2:
	fmt.Println("Two")
default:
	fmt.Println("Other")
}

โ–ถ๏ธ Running & Compiling Go Code

Run a Go file directly:

go run filename.go

Compile a Go file into a binary:

go build filename.go

This will create an executable binary with the same name as the file (without the .go extension).


โœ… Verifying Installation

go version

To check your Go environment:

go env

๐Ÿง  Pro Tip

  • Go files end with .go
  • File name doesnโ€™t need to match the function name
  • Thereโ€™s no semicolon required at the end of lines (unless you're writing multiple statements on one line)

Stay curious, and Go build cool stuff! ๐Ÿ˜Ž

๐Ÿ“ฆ Class 16 โ€” Package Scope

๐ŸŽฅ Video Title: Package scope


๐Ÿงช Code Written for This Class

add.go

package main

import "fmt"

func add(n1, n2 int) {
	res := n1 + n2
	fmt.Println(res)
}

main.go

package main

var (
	a = 20
	b = 30
)

func main() {
	add(4,7)
}

mathlib/math.go

package mathlib

import "fmt"

func Add(x int, y int) {
	z := x + y
	fmt.Println(z)
}

main.go (Modified)

package main

import (
	"fmt"
	"example.com/mathlib"
)

var (
	a = 20
	b = 30
)

func main() {
	fmt.Println("Showing Custom Package")
	mathlib.Add(4,7)
}

๐Ÿ”‘ Key Concepts

  1. Same Folder = Same Package All .go files in the same directory should have the same package name (main if you want to run them).

  2. Running Multiple Files You must include all necessary files when using go run, like:

    go run main.go add.go
    
  3. Initializing a New Module Start with:

    go mod init <module_name>
    
  4. Managing Dependencies Use:

    go get <package_name>
    go mod tidy
    
  5. Package-Level Scope Rules Only exported identifiers (functions/variables that start with a capital letter) can be accessed from outside the package.

๐Ÿง  This class was all about understanding how Go handles packages, visibility, and modular code โ€” crucial stuff for building real-world Go apps!

๐Ÿ“˜ Class 17

Video Name: Scope with another boring example ๐Ÿ™ƒ


๐Ÿง‘โ€๐Ÿ’ป Code written in this class

package main

import "fmt"

var (
	a = 10
	b = 20
)

func printNum(num int) {
	fmt.Println(num)
}

func add(x int, y int) {
	res := x + y
	printNum(res)
}

func main() {
	add(a, b)
}

๐Ÿง  Key Concepts

  1. โœ… Order doesn't matter (for package-level stuff) The order of functions and globally declared variables does not matter in Go. Even if the functions and variables are defined after main(), Go will still recognize and compile everything correctly.

  2. ๐Ÿค“ Go โ‰  Functional Paradigm Although Go has borrowed some cool ideas from functional languages (like first-class functions, closures, etc.), Go is not a functional programming language.

  3. โš–๏ธ What paradigm is Go really?

    Go is a multi-paradigm language, but its primary style is imperative and procedural, with struct-based composition over classic OOP.

It's built to be:

โœ… Simple

๐Ÿ” Predictable

๐Ÿ“– Readable

You can write in a functional-ish style, but Go wasnโ€™t designed for heavy functional abstractions.

๐Ÿ’ก Interview Question: Variable Shadowing in Go

๐Ÿงช Code Example

package main

import "fmt"

var a = 10

func main() {
	age := 30

	if age > 18 {
		a := 47        // ๐Ÿ‘‡ Shadows the global `a` ONLY inside this `if` block
		fmt.Println(a) // โžœ 47
	}

	fmt.Println(a)     // โžœ 10 (prints the global `a`)
}

๐Ÿ“Œ Takeaways:

  1. ๐Ÿ”’ Variable shadowing occurs when a local variable has the same name as a variable in an outer scope.

  2. โ›” Go won't throw an error โ€” itโ€™ll just use the innermost version in the current scope.

  3. ๐Ÿ“ฆ Global a is untouched and printed outside the if block.

  4. โœ… This behavior is intentional and useful for encapsulation and temporary overrides.

๐Ÿง  Memory & Stack Animation โ€” Step by Step

// โฑ Program Start
๐Ÿ“ฆ Data Segment:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ global a=10 โ”‚ โ—„โ”€โ”€ stays alive till program ends
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

// ๐Ÿš€ main() gets called
๐Ÿ“š Stack:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿงฉ main() Stack Frame      โ”‚
โ”‚   โ””โ”€โ”€ age = 30             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

--- age > 18 is TRUE, so we enter the `if` block ---

๐Ÿงฑ New block scope begins inside main()
๐Ÿ“š Stack:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿงฉ main() Stack Frame      โ”‚
โ”‚   โ””โ”€โ”€ age = 30             โ”‚
โ”‚   ๐Ÿ”ธ a (shadows global) =47โ”‚ โ—„โ”€โ”€ new `a` shadows the global
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ–จ๏ธ fmt.Println(a)
๐Ÿ“ค Output: 47 โœ…

--- if block ends, block-scope a is destroyed ---

๐Ÿ“š Stack:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿงฉ main() Stack Frame      โ”‚
โ”‚   โ””โ”€โ”€ age = 30             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ–จ๏ธ fmt.Println(a)
๐Ÿ“ค Output: 10 โœ… (Back to global `a`)

--- main() ends, stack is popped ---

๐Ÿ“š Stack:
(empty)

๐Ÿงผ Program exits

๐Ÿ“Œ Visualization Summary

-[] ๐Ÿง  Global variables (like a = 10) live in the data segment.

-[] ๐Ÿงต Local variables (like age or shadowed a) live in the stack.

-[] ๐Ÿ”„ When a new scope is entered (if, for, function block), it pushes new variables to the stack.

-[] โ›“๏ธ Once the block ends, the shadowed variable gets popped and memory is freed.

-[] ๐Ÿงผ At the end, the stack is cleared, but the data segment lives throughout the whole execution.

Function types

  • standard or named fucntion
  • Anonymous function
  • Function expression or assign function in variable
  • Higher order fucntion or first class function
  • Callback fucntion
  • Variadic function
  • Init function - you cannot call this, Computer calls it
  • Closure - close over
  • Defer function - last in first out
  • Receiver function or method
  • IIFE- Immediately invoked function expression

๐Ÿง  Class 19: Init Function

Video Topic: init() Function in Go


๐Ÿ”ค Code Written in This Class

//example 1
package main

import "fmt"

func main() {
	fmt.Println("Hello Init Function!")
}

func init() {
	fmt.Println("I am the function that is executed first")
}
//example 2
package main

import "fmt"

var a = 10

func main() {
	fmt.Println(a)
}

func init() {
	fmt.Println(a)
	a = 20
}

๐Ÿ” Key Concepts

  1. init() is a special Go function that runs before main(), automatically.

  2. You can have multiple init() functions across different files and packages. They all run in the order of:

    • Dependency packages first

    • File order (top to bottom) next

  3. You don't call init() manually. It runs automatically before the program starts.

๐Ÿง  CLI Memory & Execution Visualization (example 1)

Letโ€™s visualize how Go handles init() under the hood:

// ๐Ÿ›  Compile Time: Go detects init()

Found init() in main package โœ…

----------- EXECUTION BEGINS -----------

๐Ÿง  Data Segment:
(none)

๐Ÿ“š Stack:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿงฉ init()           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ–จ๏ธ Output:
"I am the function that is executed first"

๐Ÿ‘‹ init() returns

๐Ÿ“š Stack:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿงฉ main()           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ–จ๏ธ Output:
"Hello Init Function!"

โœ… Program ends gracefully

๐Ÿ” CLI Visualization: Execution & Memory Layout (example 2)

=========== Program Compilation ===========
Found global variable: a = 10
Found init() โœ…
Found main() โœ…

=========== Execution Begins ==============

๐Ÿง  Data Segment (Globals):
a = 10 โ† initialized before anything runs

๐Ÿ“š Stack Frame:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  init()    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ” init() runs
โ†’ Prints: 10
โ†’ Updates a = 20

Stack after init():
(returns to runtime)

๐Ÿ“š Stack Frame:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  main()    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ” main() runs
โ†’ Prints: 20

=========== Execution Ends ================

๐Ÿ“Œ Summary

    โœ… Global variable a is initialized before any function runs.

    โš™๏ธ init() executes first:

        Reads a = 10

        Changes a = 20

    ๐Ÿงจ main() sees updated value: 20

This is a classic example of how init() can prepare or modify the runtime environment before the actual program logic in main() kicks in.

โšก Quick Recap

  1. โœ… init() always runs before main() even if itโ€™s written after main() in your code.

  2. โ›“๏ธ You can use it to initialize configs, connections, default values, etc.

  3. ๐Ÿ’ก A Go file can have at most one main(), but multiple init()s.

๐Ÿงช "Init is like the secret backstage crew. You donโ€™t see them during the show, but theyโ€™re the reason the lights come on."

๐Ÿ“˜ Class 21 โ€“ Expressions, Anonymous Functions & IIFE in Go

๐ŸŽฅ Video Name:

Anonymous function, Expression & IIFE


๐Ÿ“ฆ Code Written in This Class

// Anonymous function
// IIFE - Immediately Invoked Function Expression

package main

import "fmt"

func main() {
	// Anonymous function
	func(a int, b int) {
		c := a + b
		fmt.Println(c)
	}(5, 7) // IIFE
}

func init() {
	fmt.Println("I'll be called first")
}

๐Ÿง  Key Concepts

๐Ÿงฎ Expression in Go

An expression is any snippet of code that evaluates to a value.

Examples:

a + b          // is an expression
func(x, y){}   // is a function expression

Expressions can be used as values, passed around, or even executed immediately โ€” which leads us toโ€ฆ

๐Ÿง™ Anonymous Function

An anonymous function is a function without a name.

Instead of:

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

You write:

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

โœ… You can assign it to a variable, pass it as an argument, or invoke it on the spot.

โšก IIFE (Immediately Invoked Function Expression)

An IIFE is an anonymous function that is executed immediately right after it's defined.

Syntax:

func(a int, b int) {
	// do stuff
}(5, 7)

Use-case: You want to run a small block of logic immediately, without polluting the namespace with a new function name.

๐Ÿ–ฅ๏ธ CLI-style Execution Visualization

=========== Compilation Phase =============
Found init() โœ…
Found main() โœ…

=========== Execution Phase ===============

๐Ÿ” init() runs first
โ†’ Prints: I'll be called first

๐Ÿง  Data Segment:
(No global vars in this case)

๐Ÿ“š Stack Frame:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    main()           โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚  anonymous func โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

main() calls an IIFE:
โ†’ Passes 5 and 7
โ†’ Inside IIFE: c := 5 + 7 = 12
โ†’ Prints: 12

=========== Execution Complete =============

๐Ÿงต TL;DR

-[] โœ… Expressions return values and can be assigned or executed.

-[] ๐Ÿงช Anonymous functions have no name, great for quick logic blocks.

-[] ๐Ÿš€ IIFE: Define & execute in one go. Great for one-off logic.

๐Ÿ“˜ Class 22 โ€“ Function Expressions & Shadowing in Go

๐ŸŽฅ Video Name:

Function Expression Example


โœ… Code 1: Working Example

package main

import "fmt"

// Global function expression
var add = func(x, y int) {
	fmt.Println(x + y)
}

func main() {
	add(4, 7) // Calls the global `add`

	// Function expression assigned to local variable
	add := func(a int, b int) {
		c := a + b
		fmt.Println(c)
	}

	add(2, 3) // Calls the local `add`
}

func init() {
	fmt.Println("I will be called first")
}

๐Ÿง  Key Concepts

๐Ÿ”ง Function Expression

A function assigned to a variable. It allows us to:

-[] Store logic in a variable

-[] Treat functions like first-class citizens

-[] Create inline, nameless (anonymous) functions

Example:

add := func(a int, b int) {
	fmt.Println(a + b)
}

๐Ÿงฑ Shadowing

When a variable in a smaller (local) scope has the same name as one in a larger (outer) scope, it "shadows" or hides it temporarily.

In the main() function:

add := func(a int, b int) {...}

This local add shadows the global add from that point onward.

๐Ÿ–ฅ๏ธ Execution Visualization (Working Example)

========== Compilation Phase ==========
โœ” Found init()
โœ” Found main()
โœ” Global `add` assigned to function

========== Execution Begins ===========

init():
โ†’ Prints: I will be called first

main():
โ†’ Calls global `add(4, 7)` โ†’ Prints: 11

Local Scope in main():
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Stack Frame โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ main()                     โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚
โ”‚ โ”‚ add (local)  โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
       (shadows global) โ—„โ”€โ”€โ”€โ”˜

โ†’ Calls local `add(2, 3)` โ†’ Prints: 5

========== Execution Ends ==========

โŒ Code 2: Fails to Compile

package main

import "fmt"

// Global function expression
var add = func(x, y int) {
	fmt.Println(x + y)
}

func main() {
	adder(4, 7) // โŒ ERROR: undefined: adder

	// Function expression or Assign function in variable
	adder := func(a int, b int) {
		c := a + b
		fmt.Println(c)
	}

	add(2, 3)
}

func init() {
	fmt.Println("I will be called first")
}

โŒ Why it fails

This line:

adder(4, 7)

is above the declaration:

adder := func(a int, b int) { ... }

โ›” The Problem: Temporal Dead Zone

In Go, you can't use a variable before it's declared, even if itโ€™s in the same block.

So, when you try to use adder, it hasnโ€™t been declared yet. Hence:

./main.go:10:2: undefined: adder

๐Ÿ“š TL;DR

Concept | Meaning Function Expression | A function assigned to a variable Anonymous Function | A function with no name Shadowing | Local variable hides the same-named global one Temporal Dead Zone | You can't use variables before their declaration in Go IIFE vs Assignment | IIFE executes immediately; assignment waits to be called explicitly

Class 23: Functional Programming Concepts in Go

Code Example

package main

import "fmt"

func add(a int, b int) { // Parameter: a and b
	c := a + b
	fmt.Println(c)
}

func main() {
	add(2, 5) // 2 and 5 are arguments
	processOperation(4, 5, add)
	sum := call() // function expression
	sum(4, 7)
}

func processOperation(a int, b int, op func(p int, q int)) { // Higher order function
	op(a, b)
}

func call() func(x int, y int) {
	return add
}

๐Ÿง  Key Concepts

1. Parameter vs Argument

  • Parameter: The variable listed inside the function definition. (e.g., a int, b int in add(a, b))
  • Argument: The actual value passed to the function when it's called. (e.g., add(2, 5))

2. First Order Function

A regular function that does not take another function as input or return one.

  • Examples:
    • Named function: func add(a, b int)
    • Anonymous function: func(a int, b int) { ... }
    • IIFE (Immediately Invoked Function Expression): func(a, b int) { ... }(5, 7)
    • Function expression: sum := func(a, b int) { ... }

3. Higher Order Function

A function that takes a function as a parameter, returns a function, or both.

  • Example:
    • processOperation takes a function op as a parameter
    • call() returns a function add

4. Callback Function

  • A function that is passed into another function to be executed later.
  • In processOperation(4, 5, add), the function add is a callback.

5. First-Class Citizen (Function)

  • In Go, functions can be assigned to variables, passed as arguments, and returned from other functions.
  • This makes them first-class citizens.

๐Ÿง  Conceptual Context (Functional Paradigm)

Functional programming treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

Inspiration from Mathematics

  • First Order Logic: Objects with properties (e.g., Person, Car, etc.)
  • Higher Order Logic: Functions and their relation with other functions (like in Go's higher-order functions)

Languages like Haskell, Racket, etc., are built on deep functional paradigms.

Go borrows some of these concepts, but it is still imperative and procedural by nature.


๐Ÿ“Ÿ CLI Visualization (Call Stack + Segments)

1. Data Segment

  • add (global function definition)
  • call (returns a function)
  • processOperation (stored function)

2. Code Execution Flow (Stack Frames)

Call Stack:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ main()                   โ”‚
โ”‚ โ”œโ”€โ”€ add(2, 5)            โ”‚ => prints 7
โ”‚ โ”œโ”€โ”€ processOperation     โ”‚
โ”‚ โ”‚   โ””โ”€โ”€ op(4, 5) => add  โ”‚ => prints 9
โ”‚ โ”œโ”€โ”€ call()               โ”‚ => returns add
โ”‚ โ””โ”€โ”€ sum(4, 7)            โ”‚ => prints 11
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Everything runs in the order written, but since functions are first-class, Go can pass and return them like variables.


Summary

  • ๐ŸŒฑ Go supports functional programming concepts like first-class and higher-order functions.
  • ๐Ÿ’ก You can pass around functions like variables โ€” extremely powerful for modular and clean code.
  • ๐Ÿง  Understanding first order vs higher order functions, parameters vs arguments, and callback functions gives you a major edge in writing elegant Go code.

โœ… This was a big brain class. You crushed it!


Class 24 โ€” Go Internal Memory (Code, Data, Stack, Heap)

๐Ÿง  Topics Covered

This class dives deep into how Go programs are structured in memory. Concepts explained include:

  • Code Segment: Stores compiled instructions (functions).
  • Data Segment: Stores global/static variables (like var a = 10).
  • Stack: Stores local function variables. Each function call creates a new stack frame.
  • Heap: Used for dynamically allocated memory (we'll explore this more later).
  • Garbage Collector: Runs on the heap. Cleans up memory that's no longer in use.

๐Ÿ“œ Code from Class 24

package main

import "fmt"

var a = 10

func add(x, y int) {
	z := x + y
	fmt.Println(z)
}

func main() {
	add(5,4)
	add(a,3)
}

func init() {
	fmt.Println("Hello")
}

๐Ÿ” Code Execution Flow & Memory Layout

           โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
           โ”‚               Code Segment                 โ”‚
           โ”‚--------------------------------------------โ”‚
           โ”‚ Functions: init, main, add                 โ”‚
           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ”‚
                          โ–ผ
           โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
           โ”‚              Data Segment                  โ”‚
           โ”‚--------------------------------------------โ”‚
           โ”‚ Global Variable: a = 10                    โ”‚
           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ”‚
                          โ–ผ
              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
              โ”‚          Stack             โ”‚
              โ”‚----------------------------โ”‚
              โ”‚ main() Stack Frame         โ”‚
              โ”‚   - Calls add(5, 4)        โ”‚
              โ”‚       - x=5, y=4           โ”‚
              โ”‚       - z=9                โ”‚
              โ”‚   - Calls add(10, 3)       โ”‚
              โ”‚       - x=10, y=3          โ”‚
              โ”‚       - z=13               โ”‚
              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ”‚
                          โ–ผ
           โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
           โ”‚               Heap (Unused here)           โ”‚
           โ”‚       (Managed by the Garbage Collector)   โ”‚
           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โš™๏ธ Execution Order

  1. init() is run automatically before main() โ†’ prints:
Hello
  1. main() runs and calls: -[] add(5, 4) โ†’ prints:
    9
    
    -[] add(a, 3) โ†’ uses a = 10 โ†’ prints:
    13
    

๐Ÿ“Œ Key Concepts Recap

Concept | Meaning Code Segment | Where all functions live after compilation Data Segment | Stores global variables Stack | Temporary memory for function execution (local vars, params) Heap | For dynamic memory (we didn't use heap explicitly here) Garbage Collector | Automatically manages memory on the heap init() Function | Special function in Go โ€” runs before main()

๐Ÿงผ Garbage Collector Insight: Goโ€™s GC sits on the heap and sweeps unused allocations to keep memory clean. You won't notice it in this small program, but it's your bestie when your app scales.

Class 25 - Internal Memory Deep Dive: Compilation & Execution Phases


โœจ Topics Covered

This class focused on the internal workings of a Go program with a spotlight on what happens under the hood during:

  • Compilation Phase
  • Execution Phase
  • How Go builds a binary with go build
  • What gets stored in that binary (functions, constants, globals, etc.)
  • How function expressions (e.g., add := func(...)) are treated in memory

๐Ÿ‘ Key Concepts

ConceptExplanation
Compilation PhaseParses and compiles source code into a binary executable. No code runs yet.
Execution PhaseRuns the compiled binary, starting from init() and then main().
Code SegmentWhere compiled functions (like main, call, and anonymous functions) live.
Data SegmentHolds global variables and constants (like p and a).
Function ExpressionsTreated as runtime function objects, stored in code segment.

๐Ÿ“‹ Code Used in Class 25

package main

import "fmt"

const a = 10 // constant
var p = 100

func call() {
	add := func(x int, y int) {
		z := x + y
		fmt.Println(z)
	}

	add(5, 6)
	add(p, a)
}

func main() {
	call()
	fmt.Println(a)
}

func init() {
	fmt.Println("Hello")
}

๐Ÿ”„ Compilation Phase Visualized

go build main.go

  • Parser/Compiler checks for syntax, scope, and dependencies.
  • Stores:
    • Constants: a = 10
    • Globals: p = 100
    • Functions: init, main, call, and the anonymous add function inside call
  • Generates a binary that includes all necessary machine code + metadata.

Binary includes:

  • Code Segment: main, call, anonymous function
  • Data Segment: const a, var p
  • No execution happens here.

โฑ Execution Phase Visualized

   1. init()            => "Hello"
   2. main()            => calls call()
   3. call()            => declares and invokes add()
       - add(5, 6)      => 11
       - add(100, 10)   => 110
   4. fmt.Println(a)    => 10

๐Ÿง  Memory Layout

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚       Code Segment          โ”‚
โ”‚-----------------------------โ”‚
โ”‚ main, call, init, add-func  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚       Data Segment          โ”‚
โ”‚-----------------------------โ”‚
โ”‚ const a = 10                โ”‚
โ”‚ var p = 100                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚           Stack             โ”‚
โ”‚-----------------------------โ”‚
โ”‚ call() frame โ†’ add func     โ”‚
โ”‚   x=5,y=6,z=11               โ”‚
โ”‚   x=100,y=10,z=110           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ”น Summary

  • Go runs in two phases: Compilation and Execution.
  • During compilation, Go prepares memory layout, compiles functions and expressions.
  • In execution, it runs init() and then main().
  • Function expressions like add := func(...) are first-class values and live in the code segment.
  • The resulting binary from go build holds everything: code, data, metadata.

Class 26: Closure & Go Internal Memory Deep Dive ๐Ÿ’ก

Welcome to Class 26, where we uncover the magic behind closures in Go, escape analysis, and how memory is managed under the hood! ๐Ÿง ๐Ÿ”ฅ


๐Ÿงพ The Code

package main

import "fmt"

const a = 10
var p = 100

//Closure
func outer(money int) func() {
	age := 30
	fmt.Println("Age =", age)

	show := func() {
		money = money + a + p
		fmt.Println(money)
	}

	return show
}

func call() {
	incr1 := outer(100)
	incr1() // money = 100 + 10 + 100 = 210
	incr1() // money = 210 + 10 + 100 = 320

	incr2 := outer(100)
	incr2()
	incr2()
}

func main() {
	call()
}

func init() {
	fmt.Println("=== Bank ===")
}

๐Ÿ” Key Concepts

๐Ÿ”’ What is a Closure?

A closure is a function that references variables from outside its own scope. In this case:

show := func() {
    money = money + a + p
    fmt.Println(money)
}

show forms a closure by capturing the money variable defined in outer().

๐Ÿง  Why is Closure Important?

Closures let you encapsulate logic along with state. This is why incr1() and incr2() maintain separate money values even though they use the same function.

๐Ÿงฎ Stack vs Heap

  • Stack: Fast memory, used for function calls and local variables.
  • Heap: Used when variables need to persist beyond the function call (like in closures!).

Because money needs to stick around after outer() returns, escape analysis detects this and allocates money on the heap.

๐Ÿงช What is Escape Analysis?

Escape analysis is the process that the Go compiler uses during the compilation phase to determine whether variables can be safely allocated on the stack or must go to the heap.

  • โœ… If a variable is used only inside a function, it's put on the stack.
  • ๐Ÿš€ If a variable is used outside (like in a returned closure), it's moved to the heap.

๐Ÿงฑ Memory Segments

SegmentWhat's Stored
Code SegmentCompiled instructions (functions)
Data SegmentGlobal and static variables (a, p)
StackLocal variables (age)
HeapEscaping variables (money)

๐Ÿง  Visualization

CLI-Style Memory Layout

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚       Code Segment          โ”‚
โ”‚-----------------------------โ”‚
โ”‚ main, call, init, outer,    โ”‚
โ”‚ anonymous show function     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚       Data Segment          โ”‚
โ”‚-----------------------------โ”‚
โ”‚ const a = 10                โ”‚
โ”‚ var p = 100                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚           Stack             โ”‚
โ”‚-----------------------------โ”‚
โ”‚ outer() frame               โ”‚
โ”‚   age = 30                  โ”‚
โ”‚   return address            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚            Heap             โ”‚
โ”‚-----------------------------โ”‚
โ”‚ money = 100 (for incr1)     โ”‚
โ”‚ money = 100 (for incr2)     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Each closure has its own money on the heap. Every call to outer(100) results in a new memory block being allocated.

Garbage Collectorโ€™s Role ๐Ÿงน

When the closure is no longer referenced (e.g., incr1 or incr2 goes out of scope), the Garbage Collector detects that the heap memory (e.g., money) is unreachable. It then safely reclaims that memory so your program doesnโ€™t become a memory hoarder. This is vital for maintaining efficiency, especially when many closures are involved.

GC is triggered automatically and runs concurrently with your program. It uses a combination of mark-and-sweep and concurrent garbage collection techniques to do this efficiently.


๐Ÿง  TL;DR

  • Closures can capture and remember variable state ๐Ÿ”
  • Escape analysis figures out which variables must live on the heap ๐Ÿ“ฆ
  • Stack is temporary, heap is persistent (with GC ๐Ÿงน)
  • Go separates memory into Code, Data, Stack, Heap โ€” each with its role ๐Ÿงฉ
  • GC ensures unused heap memory (like old closure data) is recycled โ™ป๏ธ

Class 27: Structs & Memory Layout in Go ๐Ÿงฑ

Welcome to Class 27! Today we're diving into structs, how to define and instantiate them, and how they interact with Go's memory model. Let's visualize everything from scratch like pros. ๐Ÿง ๐Ÿ’ก


โœ๏ธ The Code

package main

import "fmt"

type User struct {
	Name string
	Age  int
}

func (usr User) printDetails() {
	fmt.Println("Name:", usr.Name)
	fmt.Println("Age:", usr.Age)
}

func main() {
	user1 := User{
		Name: "Ruhin",
		Age:  21,
	}

	user2 := User{
		Name: "Mukim",
		Age:  15,
	}

	user1.printDetails()
	user2.printDetails()
}

๐Ÿง  Key Concepts

๐Ÿงฉ What is a Struct?

A struct is a user-defined type in Go used to group related data together. Itโ€™s like a custom container for fields.

type User struct {
	Name string
	Age  int
}

This defines a new type called User with fields Name and Age.


๐Ÿ”จ Creating Instances (Instantiation)

When we create an actual value using a struct type, thatโ€™s called instantiating.

user1 := User{
	Name: "Ruhin",
	Age:  21,
}

Here user1 is an instance of User. This allocates memory to hold Name and Age values.


๐Ÿง  Memory Layout (Visualization)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚       Code Segment          โ”‚
โ”‚-----------------------------โ”‚
โ”‚ main, printDetails,         โ”‚
โ”‚ type User struct {...}      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚       Data Segment          โ”‚
โ”‚-----------------------------โ”‚
โ”‚ -                           โ”‚
โ”‚ (Global vars if present)    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚           Stack             โ”‚
โ”‚-----------------------------โ”‚
โ”‚ main() frame โ†’              โ”‚
โ”‚   user1 โ†’ Name: "Ruhin"     โ”‚
โ”‚           Age: 21           โ”‚
โ”‚   user2 โ†’ Name: "Mukim"     โ”‚
โ”‚           Age: 15           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โš ๏ธ NOTE: If a struct is returned from a function or captured by a closure, it may escape to the heap instead of stack.


๐Ÿ“‹ Example Use Case

type Book struct {
	Title  string
	Author string
	Pages  int
}

book1 := Book{
	Title: "1984",
	Author: "George Orwell",
	Pages: 328,
}

This lets us build real-world models with multiple fields.


๐Ÿงน Role of the Garbage Collector (GC)

  • If a struct instance escapes (used outside the function, stored long-term, etc.), Go stores it on the heap.
  • Goโ€™s garbage collector then tracks and cleans it when itโ€™s no longer in use.
  • This means you donโ€™t have to manually free() anything โ€” Go handles memory cleanup for heap objects.

๐Ÿš€ TL;DR

  • type User struct {...} is metadata โ†’ stored in the Code Segment.
  • user1 := User{...} is runtime data โ†’ stored in Stack or Heap depending on usage.
  • Structs bundle fields into one logical unit โœ…
  • Memory layout varies depending on usage โ†’ escape analysis decides ๐Ÿ“ฆ๐Ÿงณ
  • GC only manages objects in the heap, not on the stack ๐Ÿงน

Q: Is struct a datatype?

Ans: Yes, 100% โ€” a struct in Go is a user-defined data type. Think of it like creating your own custom "blueprint" for a data object. ๐Ÿ’ก

Here's how it fits in:

-[] Go has primitive data types like int, string, bool, etc.

-[] You can then use struct to define a custom data type that groups multiple fields together.

For example:

type User struct {
	Name string
	Age  int
}

This User struct becomes its own data type, and now you can create instances of it just like you would for int or string:

var u User
u.Name = "Ruhin"
u.Age = 21

Itโ€™s like building your own Lego brick with a custom shape, and then making as many copies of that brick as you want. ๐Ÿงฑโœจ

Youโ€™re now struct-urally sound in Go! ๐Ÿ˜Ž Next time you model data, flex your type muscles and track those memory segments like a boss.

Class 28: Receiver Functions in Go

๐Ÿ”‘ Key Concept: Receiver Functions

In Go, a receiver function (also called a method) is a function that is associated with a particular type (usually a struct). It allows us to add behavior to data types, like attaching functions to objects in other languages (e.g., methods in OOP).


๐Ÿง  What Is a Receiver Function?

A receiver function is defined like a normal function, but with a special receiver parameter placed between the func keyword and the function name.

func (r ReceiverType) FunctionName(params) returnType {
    // function body
}

The receiver type can be:

  • A value receiver: (t Type) โ†’ receives a copy
  • A pointer receiver: (t *Type) โ†’ receives a reference (can modify original)

๐Ÿ—๏ธ From the Project Code

func (todos *Todos) add(title string) {
    todo := Todo{
        Title: title,
        Completed: false,
        CompletedAt: nil,
        CreatedAt: time.Now(),
    }
    *todos = append(*todos, todo)
}
  • todos *Todos is the receiver
  • This method is attached to Todos (which is a custom type: []Todo)
  • The *Todos pointer allows modifications to the original slice

Example usage from main.go:

todos.add("Buy milk")

๐Ÿ” Why Use Receiver Functions?

  • Organize logic with the data it operates on โœ…
  • Achieve OOP-like behavior in Go โœ…
  • Maintain cleaner and modular code โœ…

๐Ÿ’ก Extra Simple Example

type User struct {
    Name string
}

// Value receiver (no change to original)
func (u User) SayHi() {
    fmt.Println("Hi, I am", u.Name)
}

// Pointer receiver (can change original)
func (u *User) ChangeName(newName string) {
    u.Name = newName
}

func main() {
    user := User{Name: "Ruhin"}
    user.SayHi() // Hi, I am Ruhin
    user.ChangeName("Mukim")
    user.SayHi() // Hi, I am Mukim
}

โš™๏ธ Summary

TermMeaning
ReceiverThe type a method is attached to (e.g., *Todos)
Value ReceiverGets a copy of the value; doesn't affect the original
Pointer ReceiverGets a reference; can modify the original

๐Ÿ“˜ Visualizing It

Think of todos.add() as calling a behavior of the object:

object.method()

This pattern lets Todos have its own custom logic, like add, delete, toggle, print, etc., just like class methods in Python/Java.


Class 29 - Go Arrays and Memory Layout

๐Ÿ“… Date: April 24, 2025

๐Ÿ”‘ Key Concepts

โœ… What is an Array?

  • An array is a fixed-size collection of elements of the same type.
  • In Go, arrays are value types, meaning they are copied when passed around.

๐Ÿง  Array in Go

var arr [2]int       // Declares an array of 2 integers. Default values: [0, 0]
arr[0] = 3           // Assigning values using index
arr[1] = 6

// Short way of declaring an array with values
arr := [2]int{3, 6}

๐Ÿ’ก Indexing

  • Arrays in Go are zero-indexed, meaning the first element is accessed with array[0].

โš™๏ธ Default Values

  • If you declare an array without initializing it, Go assigns default values:
    • For int, float, etc: 0
    • For string: "" (empty string)
    • For bool: false
    • For pointers/interfaces: nil

๐Ÿ” Memory Layout Visualization

Example:

arr := [2]int{3, 6}

Memory Layout

AddressValueMeaning
0x10003arr[0]
0x10046arr[1]

Note: The actual address is abstract. The concept is: array elements are stored contiguously in memory.

Another example:

arr2 := [3]string{"I", "love", "you"}
IndexValue
0"I"
1"love"
2"you"

Accessing arr2[1] returns "love".


๐Ÿงช Full Code Example (From Class)

package main

import "fmt"

var arr2 = [3]string{"I", "love", "you"}

func main() {
    arr := [2]int{3,6}
    fmt.Println(arr)
    fmt.Println(arr2)
    fmt.Println(arr2[1])
}

๐Ÿ“ฆ Summary

  • Arrays are great for working with fixed-size collections.
  • Be aware of default values.
  • They're stored contiguously in memory.
  • Go makes it easy to work with arrays, and it's a good base before moving to slices!

Class 30: Pointers in Go

What is a Pointer?

A pointer is a variable that stores the memory address of another variable.

In Go, memory is divided into several segments:

  • Code segment: Stores compiled program instructions (functions).
  • Data segment: Stores global/static variables and constants.
  • Heap: Stores dynamically allocated memory.
  • Stack: Stores local variables and function call information.

Pointers help us interact directly with memory addresses.


Symbols to Know:

  • & (Ampersand): Used to get the address of a variable.
  • * (Star/Dereference operator): Used to get the value stored at a memory address.

Example:

x := 20
p := &x // p holds the address of x

*p = 30 // change value at address p (which changes x)

fmt.Println(x)  // 30
fmt.Println(p)  // address of x
fmt.Println(*p) // 30 (value at address)

Why Use Pointers?

  • Efficiency: Instead of copying big structures (like arrays), just pass their memory address.
  • Shared Modification: If multiple functions need to modify the same data.
  • Memory Management: Especially important in lower-level or high-performance programming.

Without pointers, every function call would copy entire objects. That's sloooow and wasteful!


Pass by Value vs Pass by Reference

Pass by Value:

  • A copy of the variable is passed.
  • Changes inside the function don't affect the original.
func print(numbers [3]int) {
	fmt.Println(numbers)
}

arr := [3]int{1, 2, 3}
print(arr) // Passing a copy

Pass by Reference:

  • Pass the address instead of copying.
  • Changes inside the function affect the original.
func print2(numbers *[3]int) {
	fmt.Println(numbers)
}

arr := [3]int{1, 2, 3}
print2(&arr) // Passing a pointer

Struct Pointers (and why Go is chill with them)

When you have a pointer to a struct, Go is smart enough to let you access fields without needing * every time.

user1 := User{
	Name: "Ruhin",
	Age: 21,
	Salary: 0,
}
p2 := &user1
fmt.Println(p2.Age) // no need to write (*p2).Age

Go automatically dereferences it for you. Big W.


Full Code Example from Class:

package main

import "fmt"

type User struct {
	Name   string
	Age    int
	Salary float64
}

func print(numbers [3]int) {
	fmt.Println(numbers)
}

func print2(numbers *[3]int) {
	fmt.Println(numbers)
}

func main() {
	x := 20
	p := &x
	*p = 30
	
	fmt.Println(x)           // 30
	fmt.Println("Address:", p)
	fmt.Println("Value:", *p)

	arr := [3]int{1, 2, 3}
	print(arr)   // pass by value
	print2(&arr) // pass by reference

	user1 := User{
		Name:   "Ruhin",
		Age:    21,
		Salary: 0,
	}
	p2 := &user1
	fmt.Println(p2.Age)
}

Memory Layout Visualization (CLI-Style)

+--------------------+----------------------------------+
| Segment            | What's stored                   |
+--------------------+----------------------------------+
| Code Segment       | main(), print(), print2()        |
| Data Segment       | (none for local vars here)       |
| Stack              | arr [3]int {1,2,3}, x=30         |
|                    | p (pointer to x)                 |
|                    | user1 (User struct)              |
|                    | p2 (pointer to user1)            |
| Heap               | (unused for this simple program) |
+--------------------+----------------------------------+

Detailed Memory Visualization (Addresses and Values)

Stack Memory:

[ Address 0x1000 ] x = 30
[ Address 0x1004 ] p -> 0x1000 (address of x)
[ Address 0x1008 ] arr = [1, 2, 3]
[ Address 0x1010 ] user1 = {"Ruhin", 21, 0.0}
[ Address 0x1018 ] p2 -> 0x1010 (address of user1)

Code Segment:
- Compiled code of main, print, print2

Data Segment:
- Empty (no global variables/constants)

Heap:
- Not used in this example

Extra Example: Swapping Two Numbers with Pointers

Without Pointers (FAIL):

func swap(x, y int) {
	temp := x
	x = y
	y = temp
}

func main() {
	a, b := 1, 2
	swap(a, b)
	fmt.Println(a, b) // still 1 2
}

With Pointers (WIN):

func swap(x, y *int) {
	temp := *x
	*x = *y
	*y = temp
}

func main() {
	a, b := 1, 2
	swap(&a, &b)
	fmt.Println(a, b) // 2 1
}

Quick Summary

  • & gets the address.
  • * gets the value at an address.
  • Pointers = efficient + powerful.
  • Struct pointer fields are auto-dereferenced.
  • Pass big things (like arrays, structs) by pointer to save memory.

Bro Tip:

When in doubt, think: "Am I copying a whole dang castle, or just giving a map to it?"

Pointers = the map. โœ…

๐Ÿ“š Slice Deep Dive in Go


๐Ÿ“Œ Class 31: Slice

๐Ÿš€ Key topics

  1. What is a Slice?
  2. How many parts does a Slice have?
  3. How to determine Pointer, Length, and Capacity of a Slice?
  4. Creating a Slice from an existing Array
  5. Creating a Slice from an existing Slice
  6. Slice Literal
  7. Creating a Slice with make() (length only)
  8. Creating a Slice with make() (length and capacity)
  9. Creating an Empty or Nil Slice
  10. Appending elements to a Slice
  11. What happens internally when appending (Heap and Underlying Array behavior)
  12. How the underlying array increases dynamically
  13. Some interesting examples and interview questions
  14. Variadic Functions

๐Ÿง  1. What is a Slice?

  • A slice is a lightweight data structure in Go.
  • Think of it like a dynamic view over an array.
  • Unlike arrays, slices can grow and shrink.

Key Points:

  • Slices are not arrays.
  • Slices are built on top of arrays.

๐Ÿ”ฅ 2. How many parts does a Slice have?

Under the hood, a Slice is a struct with three fields:

struct Slice {
    pointer *T // Pointer to the underlying array
    length  int // Current number of elements
    capacity int // Maximum number of elements (until reallocation)
}

You can think of a slice as a "window" into an array.


๐Ÿ•ต๏ธโ€โ™‚๏ธ 3. How to determine Pointer, Length, and Capacity

Use:

  • len(slice) โžก๏ธ Length
  • cap(slice) โžก๏ธ Capacity

Example:

s := arr[1:4] // From index 1 to 3
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // depends on how much array is left after index 1

๐Ÿ— 4. Creating a Slice from an existing Array

arr := [6]string{"This", "is", "a", "Go", "interview", "Questions"}
s := arr[1:4] // slice ["is", "a", "Go"]
  • pointer: points to index 1 of arr
  • length: 3 (from index 1 to 3)
  • capacity: 5 (indexes 1 to 5)

๐Ÿ”„ 5. Creating a Slice from an existing Slice

s1 := s[1:2] // Slice "a"
  • This slice is again a view into the same array!
  • Changing s1 can affect arr.

โœ๏ธ 6. Slice Literal

Create a slice without needing an array explicitly.

s2 := []int{3, 4, 7}

Here Go automatically creates an underlying array.


๐Ÿ—๏ธ 7. Creating a Slice with make() (length only)

s3 := make([]int, 3)
  • Creates a slice of 3 zeroed elements.
  • len = 3, cap = 3

๐Ÿ—๏ธ๐Ÿ—๏ธ 8. Creating a Slice with make() (length and capacity)

s4 := make([]int, 3, 5)
  • len = 3, but it can grow up to cap = 5 before reallocating.

๐Ÿ•ณ 9. Creating an Empty or Nil Slice

var s5 []int
  • len = 0, cap = 0
  • Still valid! You can append to it.

โž• 10. Appending Elements to a Slice

s6 := append(s6, 1)
  • Go handles growing the underlying array if needed.
  • May involve allocating a bigger array and copying elements.

๐Ÿงฌ 11. What Happens Internally with Append

When a slice reaches capacity:

  • A new array (usually double the size) is created.
  • Old elements are copied into the new array.

This is why sometimes appending seems "fast" and sometimes causes big memory ops.


๐Ÿ“ˆ 12. How Underlying Array Increases

Capacity Growth Pattern: (simplified)

  • Cap 1 โžก๏ธ 2 โžก๏ธ 4 โžก๏ธ 8 โžก๏ธ 16 โžก๏ธ ...

This is an optimization trick to ensure appends are amortized O(1).


Go Slice Growth: Understanding the Dynamics of len and cap

Go slices are a powerful and flexible data structure, providing a dynamic array-like abstraction. One of the key features of slices is their ability to grow automatically when elements are appended. Understanding how and when a slice growsโ€”along with the mechanics of memory allocationโ€”can lead to more efficient use of slices in your programs.

In this document, we'll break down how Go slices grow, covering:

  • The doubling of capacity when the slice's len and cap are less than 1024.
  • The 25% growth for slices when the len and cap exceed 1024.
  • Why a slice doesn't grow by a fixed amount, such as increasing from 1024 to 1280, but instead grows by larger, more optimized blocks (e.g., 1536).

Slice Growth Overview

In Go, slices are backed by arrays. When you append elements to a slice, Go may allocate a new, larger array and copy the old elements into it. The key to this resizing is how Go determines the new capacity and allocates memory.

1. Doubling the Capacity for Small Slices (len(cap) < 1024)

When the slice is relatively small (i.e., when the len and cap of the slice are both smaller than 1024), the growth strategy Go uses is to double the capacity. This means that when you append an element to the slice and the slice needs to resize, it will allocate a new array that is twice the size of the current capacity. The len of the slice will increase by one, but the cap will double.

Example:

s := []int{1, 2, 3}
fmt.Println(len(s), cap(s)) // len: 3, cap: 3

s = append(s, 4)
fmt.Println(len(s), cap(s)) // len: 4, cap: 6

s = append(s, 5)
fmt.Println(len(s), cap(s)) // len: 5, cap: 12
  • Initially, the slice has a length of 3 and a capacity of 3.
  • When we append the fourth element, the slice grows to a capacity of 6 (doubling from 3).
  • The next append results in the slice growing to a capacity of 12 (doubling from 6).

2. Growth by 25% for Larger Slices (len(cap) >= 1024)

Once the slice grows to a size where its len and cap exceed or are equal to 1024, Go switches from doubling the capacity to increasing the capacity by 25% of the current capacity. This growth strategy helps to strike a balance between minimizing frequent reallocations and not wasting too much memory.

Example:

s := make([]int, 1024)  // len: 1024, cap: 1024
fmt.Println(len(s), cap(s))

s = append(s, 1025) // len: 1025, cap: 1280 (1024 + 25% of 1024)
fmt.Println(len(s), cap(s))

s = append(s, 1300) // len: 1300, cap: 1600 (1280 + 25% of 1280)
fmt.Println(len(s), cap(s))
  • Initially, we create a slice with a length and capacity of 1024.
  • When appending the next element, the slice grows to a capacity of 1280, which is 1024 plus 25% of 1024.
  • Another append results in a capacity of 1600 (1280 plus 25% of 1280).

3. The Role of Memory Blocks (e.g., 1536 for a Slice)

When the slice's len and cap are near the threshold of 1024 (and higher), Go doesn't always allocate memory blocks in neat, predictable sizes like 1280. Instead, it aligns to optimal memory blocks that align better with system memory allocation patterns.

For example, if a slice's capacity is nearing 1024, the next allocation might not simply be an increment by 256 (i.e., from 1024 to 1280). Instead, Go will allocate memory in larger chunks to optimize memory usage and alignment. A common result of this optimization is the slice's capacity growing to 1536, which is a more "perfect" memory block for larger sizes.

Why 1536 Instead of 1280?

This behavior is largely based on hardware memory alignment. The number 1536 is chosen because it fits better with memory block sizes that are typically aligned in powers of 2 and optimized for modern CPUs and memory systems. Memory allocations are often made in chunks that align with the systemโ€™s memory page size or cache line, resulting in a more efficient memory access pattern.

Example (Memory Alignment):

s := make([]int, 1024) // len: 1024, cap: 1024
fmt.Println(len(s), cap(s)) // 1024, 1024

s = append(s, 1025) // len: 1025, cap: 1536 (next optimal block size)
fmt.Println(len(s), cap(s)) // 1025, 1536
  • The capacity grows from 1024 to 1536 rather than 1280, as 1536 is a better memory block that optimizes system memory allocation.

4. Why Does This Happen?

The reason Go doesn't strictly grow the slice by 256 (as one might expect, like going from 1024 to 1280) is due to efficiency considerations. The allocation strategy aims to reduce the number of reallocations while not wasting memory. By allocating a larger chunk (1536 in this case), the Go runtime ensures that the slice has enough room to accommodate several more appends without needing to resize again too soon.

This leads to better performance, especially in cases where slices grow rapidly.

Conclusion

Understanding slice growth behavior can help you write more efficient Go code. When the slice is smaller, Go doubles its capacity to handle more elements with fewer reallocations. When the slice reaches a certain size (1024 and beyond), it increases capacity by 25%, and occasionally, it aligns the slice's capacity with optimal memory block sizes for better efficiency. This approach leads to smoother and more performant memory handling, ensuring that slices are both memory-efficient and fast to work with.


๐Ÿคฏ 13. Interesting Interview Question Examples

โšก Same Underlying Array Trick

var x []int
x = append(x, 1)
x = append(x, 2)
x = append(x, 3)

y := x
x = append(x, 4)
y = append(y, 5)

x[0] = 10
fmt.Println(x)
fmt.Println(y)
  • x and y were sharing the same backing array.
  • Mutating one could affect both.

After appending past the cap, they might split into their own arrays.


๐Ÿ›  14. Variadic Functions

Functions can accept an arbitrary number of arguments with ....

func variadic(numbers ...int) {
    fmt.Println(numbers)
}

variadic(2, 3, 4, 6, 8, 10)

Internally, numbers is just a slice!


๐Ÿง  Visualizing Slice in RAM (for arr and s)

Array arr (indexes):
[0] "This"
[1] "is"  <- s.ptr points here
[2] "a"
[3] "Go"
[4] "interview"
[5] "Questions"

Slice s:
- ptr = &arr[1]
- len = 3 ("is", "a", "Go")
- cap = 5 (from "is" to "Questions")

Memory Visualization:

+---+---+---+---+---+---+
|This|is|a|Go|interview|Questions|
+---+---+---+---+---+---+
     ^   ^   ^
     s[0] s[1] s[2]

๐Ÿ“„ Full Code with Detailed Comments

package main

import "fmt"

func main() {
	// Create an array of strings
	arr := [6]string{"This", "is", "a", "Go", "interview", "Questions"}
	fmt.Println(arr)

	// Create a slice from array indexes 1 to 3 (exclusive of 4)
	s := arr[1:4]
	fmt.Println(s) // [is a Go]

	// Create a slice from a slice
	s1 := s[1:2]
	fmt.Println(s1) // [a]
	fmt.Println(len(s1)) // 1
	fmt.Println(cap(s1)) // 4 (capacity depends on the underlying array)

	// Slice literal
	s2 := []int{3, 4, 7}
	fmt.Println("slice", s2, "lenght:", len(s2), "capacity:", cap(s2))

	// make() function with length only
	s3 := make([]int, 3)
	s3[0] = 5
	fmt.Println(s3)
	fmt.Println(len(s3))
	fmt.Println(cap(s3))

	// make() function with length and capacity
	s4 := make([]int, 3, 5)
	s4[0] = 5
	fmt.Println(s4)
	fmt.Println(len(s4))
	fmt.Println(cap(s4))

	// Empty slice
	var s5 []int
	fmt.Println(s5) // []

	// Appending elements to empty slice
	var s6 []int
	s6 = append(s6, 1)
	fmt.Println(s6) // [1]

	var s7 []int
	s7 = append(s7, 1, 2, 3)
	fmt.Println(s7, len(s7), cap(s7)) // [1 2 3] 3 3

	// Interview question: Sharing underlying array
	var x []int
	x = append(x, 1)
	x = append(x, 2)
	x = append(x, 3)

	y := x
	x = append(x, 4)
	y = append(y, 5)

	x[0] = 10

	fmt.Println(x) // [10 2 3 5]
	fmt.Println(y) // [10 2 3 5]

	// Another interview question
	slc := []int{1, 2, 3, 4, 5}
	slc = append(slc, 6)
	slc = append(slc, 7)

	slcA := slc[4:]

	slcY := changeSlice(slcA)

	fmt.Println(slc)  // [1 2 3 4 10 6 7]
	fmt.Println(slcY) // [10 6 7 11]
	fmt.Println(slc[0:8]) // [1 2 3 4 10 6 7 11]

	// Variadic function call
	variadic(2, 3, 4, 6, 8, 10)
}

// Function that changes the slice passed
func changeSlice(a []int) []int {
	a[0] = 10
	a = append(a, 11)
	return a
}

// Variadic function that takes multiple integers
func variadic(numbers ...int) {
	fmt.Println(numbers)
	fmt.Println(len(numbers))
	fmt.Println(cap(numbers))
}

Experiment

This will be updated soon :3

Blank Identifier aka _

What is it and What does it do?

It is mostly used for ignoring a return value from a function (for whatever reason) It can be used in a for loop too when either value or index isn't necessary Like you know Looping when you don't really need the loop variable. For example:

for _, val := range []string{"apple", "banana", "cherry"} {
    fmt.Println(val)
}

(Here, you don't care about the index.)

It can also be used for importing packages only for their side effects Like saying "Hey Go compiler, just run it's init() code but don't give me any of it's exports."

Some more usecase of blank identifier

  1. Ignoring errors on purpose (People do this all the time)
  2. Force the compiler to chill by making the unused imports and variables _
  3. Ignoring unused loop vars
  4. Only care about some return, like suppose, I have a function where it returns 3 integer value but I need only one for some operation. so, I will just use the one in that case.
    _, _, c := getThreeValues()
    
  5. Declaring and keeping it for later uses
result := doSomething()
_ = result
//TODO: Use result later
  1. Blank Receiver Method (Super advance stuff)

Will study about this one furthur one example:

func (_ MyStruct) DoSomething() {
    fmt.Println("I don't need the object itself.")
}

Okay so now let's do some experiment

Experiment with blank identifier

Sometimes people can abuse it and it causes hilarious bug

  1. Loop variable leaks beacuse of _ abuse
  2. Blank import to secretly bring hell
  3. Blank reciever methods causing existential crisis

Experiment 02

This is the folder structure:

experiment002
---aurora
------hell.go
---go.mod
---main.go

This is the main.go

package main

import (
	"fmt"
	_"experiment/aurora"
)

func prime_checker(a int) bool {
	b := a / 2
	cnt := 0
	for i:=1;i<=b;i++{
		if a % i == 0 {
			cnt++
		}
	}
	return cnt == 1
}

func main() {
	num := 61
	if prime_checker(num) {
		fmt.Printf("%d is a prime number", num)
	}else {
		fmt.Printf("%d is not a prime number", num)
	}
}

and this hell.go inside the aurora package

package aurora

import "fmt"

func init(){
	go func(){
		for {
			fmt.Println("I LIVE IN YOUR RAM NOW")
		}
	}()
}

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

Init function Use-case 1

Setting Up Global Configuration (like reading from .env)

Let's say I have a Go web app, and you want to load environment variables once, automatically. Before actual logic starts running.

package config

import (
    "log"
    "os"
)

var (
    Port     string
    Database string
)

func init() {
    Port = os.Getenv("APP_PORT")
    if Port == "" {
        Port = "8080" // default fallback
    }

    Database = os.Getenv("DB_CONN")
    if Database == "" {
        log.Fatal("DB_CONN not set in environment variables")
    }
}

Now in main.go

package main

import (
    "fmt"
    "yourapp/config"
)

func main() {
    fmt.Println("App will run on port:", config.Port)
    fmt.Println("Connecting to database:", config.Database)
}

But of course I don't have a web app so when I run it in my terminal, it says

:!go run main.go                                                  
2025/05/14 14:04:00 DB_CONN not set in environment variables      
exit status 1                                                     
                                                                  
shell returned 1                                                  
                                                                  
Press ENTER or type command to continue 

๐Ÿง  Why use init() here?

  • It runs automatically when the package is imported.
  • You donโ€™t call it yourselfโ€”itโ€™s great for setting up global stuff once.
  • Keeps your main() clean and focused on actual execution flow.

Partial Declaration

Here's an example

package main

import "fmt"

func main() {
    a := 7
    fmt.Println(a) // 7
    {
        a := 6 //Here, a is shadowed
        fmt.Println(a) // 6
    }
    fmt.Println(a) // 7
}

so here, a got shadowed. In GO, it's not as variable shadowing!

But look at this one!

Example 2

package main

import "fmt"

func getnum() (int, int) {
    return 10, 11
}

func main() {
    a := 7 //Declaring a
    fmt.Println(a)
    a = 6 //Reassigning a, so the value of a is 6 now
    b := 8 //declaring a variable 'b'
    c, b := getnum()
    fmt.Println(b)
    fmt.Println(c)
}

so here in c, b := getnum(), do you think that b got shadowed here? nope, variable can't be shadowed in the same local scope. So what happend? this is called partial declaration. Cause, a new variable c git declared but b actually got reassigned. Simple as that.

Pointer experiment 001

CODENAME: PTR_EXP001

So when I was studying pointer, I leanred that it just points at the memory address of that particulat data. So, I thought okay so pointers in Go pass the stuff by their reference and can boost efficiency compared to pass by value especially in terms of big data structures.

Now, Why not see it practically by coding? hehe :3

Let the experiment begin

  1. I will create a big slice (pretend it's a chonk of data)
  2. Will make two function
    • One that takes it by value (makes a full copy)
    • One that takes it by reference (just a pointer)
  3. Then we will find out time of both

The code:

package main

import (
	"fmt"
	"time"
)

// function that takes a slice by value (copy)
func processByValue(data []int) {
	// Just pretend we're doing some work
	for i := range data {
		data[i] += 1
	}
}

// function that takes a slice by reference (pointer)
func processByReference(data *[]int) {
	for i := range *data {
		(*data)[i] += 1
	}
}

func main() {
	// Create a big slice (like 10 million ints)
	size := 10_000_000
	data := make([]int, size)

	// Process by value
	start := time.Now()
	processByValue(data)
	duration := time.Since(start)
	fmt.Println("Time taken by value:", duration)

	// Reset data
	for i := range data {
		data[i] = 0
	}

	// Process by reference
	start = time.Now()
	processByReference(&data)
	duration = time.Since(start)
	fmt.Println("Time taken by reference:", duration)
}

Result:

After running this it gives me output

:!go run main.go                                                  
Time taken by value passing 10.973601ms                           
Time taken by reference: 8.608471ms

Not that much difference. Only 2 secs

Then I ran it again but this time increase one 0 after 100000000

:!go run main.go                                                  
Time taken by value passing 105.022027ms                          
Time taken by reference: 74.597666ms

Now the difference is much more noticable

๐Ÿ“ฆ The Slice Capacity Growth Rules in Go

Go does not guarantee a strict rule for how slice capacity grows. But there is a pattern based on the Go runtimeโ€™s memory allocator strategy, which optimizes for speed and memory reuse. Itโ€™s not random, but itโ€™s also not always linear or simple.

๐Ÿ”น Rule 1: Capacity < 1024 โ†’ Double the capacity

If the current capacity is less than 1024:

cap = cap * 2

But this doubling can sometimes skip a step if Go's allocator decides to round up to the next power-of-two or memory block size (especially for small allocations).

Example

s := make([]int, 50) // cap = 50
s = append(s, 1)     // cap becomes 112, not 100

Why? Go uses runtime.growslice under the hood, which often overallocates to align to size classes used by Go's allocator (which uses mallocgc behind the scenes).

So 112 is chosen because it matches the next available block size for reuse. Efficient memory reallocation > predictable growth

๐Ÿ”น Rule 2: Capacity โ‰ฅ 1024 โ†’ Grow by ~25% chunks

newcap := oldcap + oldcap/4

So: - [] 1024 โ†’ 1024 + 256 = 1280 - [] But Go bumps it to 1536 because of memory allocator class alignment again (usually to avoid fragmentation and reuse existing allocation buckets)

๐Ÿ”น Rule 3: Alignment to Size Classes

This is the hidden layer that messes with predictability. Go groups allocations into size classes, and sometimes grows a slice to the next class size even if it exceeds the calculated "double" or "25% increase". These size classes are part of Go's runtime/sizeclasses.go. They are not public API but are roughly:

16, 32, 48, 64, 80, 96, 112, 128, ...

Thatโ€™s why:

cap 50 โ†’ append โ†’ cap becomes 112 (because 112 is next best fit)

๐Ÿ”Ž So how do you predict it?

You can approximate it like this:

func growCap(oldCap int) int {
	if oldCap < 1024 {
		return nextSizeClass(oldCap * 2)
	}
	return nextSizeClass(oldCap + oldCap/4)
}

func nextSizeClass(n int) int {
	// These are simplified class sizes, not exact.
	classes := []int{16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192,
		208, 224, 240, 256, 320, 384, 448, 512, 576, 640,
		704, 768, 832, 896, 960, 1024, 1152, 1280, 1408,
		1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328,
		3584, 3840, 4096, 4608, 5120, 5632, 6144, 6656,
		7168, 7680, 8192}

	for _, c := range classes {
		if c >= n {
			return c
		}
	}
	return n // fallback if class not found
}

That would get you very close to how Go grows slices.

This can change anytime

The Go team can tweak the growth rules in future versions of Go to improve memory performance. So donโ€™t rely on this behavior in production code for anything criticalโ€”just treat it as an optimization detail.

Input and Output Method in Golang

Why it's necessary?

We often work with data no matter which sector we are working on Computer Science. Specially as a competitive programmer, you often have to work with different kind of input and output type. Today, we will have a look into those.

๐Ÿง  TL;DR of I/O in Go:

Goโ€™s standard input/output is safe and readable, but slow for competitive programming or heavy I/O use. Hence, people use Fast I/O with bufio

๐Ÿ”ธ 1. Standard Input/Output

Goโ€™s basic I/O uses:

  • fmt.Scan, fmt.Scanf, fmt.Scanln

  • fmt.Print, fmt.Printf, fmt.Println

Example:

var n int
fmt.Scan(&n)
fmt.Println(n)

Pros: Simple and easy.

Cons: Slow due to lots of syscalls and formatting.

๐Ÿ”ธ 2. Buffered I/O using bufio

bufio.NewReader and bufio.NewWriter buffer input/output, reducing syscall overhead = โšก๏ธfaster I/O.

reader := bufio.NewReader(os.Stdin)
writer := bufio.NewWriter(os.Stdout)
package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	in := bufio.NewReader(os.Stdin)
	out := bufio.NewWriter(os.Stdout)
	defer out.Flush() // ensures everything is printed at the end

	line, _ := in.ReadString('\n')       // read a full line from stdin
	line = strings.TrimSpace(line)       // remove trailing newline/whitespace
	a, _ := strconv.Atoi(line)           // convert string to int
	fmt.Fprintln(out, a)                 // write to buffered output
}

ReadString('\n') reads until newline. writer.Flush() is needed to print output.

๐Ÿ”ธ 3. os Package

For even lower-level access:

  • os.Stdin.Read()
  • os.Stdout.Write()

Rarely used unless youโ€™re doing file I/O or raw bytes

file, _ := os.Create("output.txt")
fmt.Fprint(file, "This is how you write to a file, by the way")
file.Close()

Using os.Create and os.Open to write and read from file:

Letโ€™s say you're writing some lines to a file and then reading them back.

Writing to a File (Buffered Output)

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	// Create file
	file, err := os.Create("example.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	// Create a buffered writer
	writer := bufio.NewWriter(file)

	// Write some lines
	for i := 1; i <= 5; i++ {
		fmt.Fprintf(writer, "This is line %d\n", i)
	}

	// Flush everything to file
	writer.Flush()
}

This will create a file called example.txt with 5 lines. The data is stored in a buffer first and only written to disk when Flush() is called, which is more efficient.

Reading from the File (Buffered Input)

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	// Open file
	file, err := os.Open("example.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	// Create a buffered reader
	reader := bufio.NewReader(file)

	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			break // EOF reached
		}
		fmt.Print(line)
	}
}

Real-World Analogy:

Think of buffered I/O like taking notes in a notepad during a lecture. You donโ€™t run to the printer after every word. You fill up the page (buffer), then print (flush). Thatโ€™s why itโ€™s efficient.

Now what exactly is buffered I/O and a buffer

What is a buffer?

A buffer is a chunk of memory โ€” specifically, it's temporary storage used to hold data before it's read or written.

Think of it like:

"Iโ€™ll store this data in RAM for now... and when Iโ€™ve got enough, Iโ€™ll send it all in one go."

Quick Visual:

[User Input] ---> [ BUFFER (in memory) ] ---> [ Actual Program or File or OS ]

Or for output:

[Program Output] ---> [ BUFFER (in memory) ] ---> [ Screen / File / OS ]

Real World Again:

Imagine a notepad (buffer) in a restaurant:

  • You write down all orders first.

  • Once the notepad is full, or you're ready, you go to the kitchen and shout them all out at once.

The notepad = the buffer

The kitchen = the OS or final destination

A buffer is memory, used to:

  • ๐Ÿ›‘ Reduce expensive operations (like I/O syscalls)

  • ๐Ÿš€ Boost performance by batching reads/writes

Buffered vs Unbuffered I/O โ€” The Core Idea

๐Ÿ”ธ Unbuffered I/O:

Every time you read or write, it IMMEDIATELY talks to the OS.

Like:

  • "Yo OS, give me 1 byte"
  • "Now give me another..."
  • "Now print this one line..."

Thatโ€™s slow, because system calls are expensive. Too many = ๐ŸŒ

๐Ÿ”ธ Buffered I/O:

You don't ask the OS every single time. Instead, you collect a bunch of input/output in memory (a buffer), and once it's full or you're done, then you send it all at once

๐Ÿ’ฅ Think of it like:

  • "Lemme fill this bucket with water (data)..."

  • "Once it's full, Iโ€™ll throw it at the OS!"

๐Ÿงƒ Real World Analogy

Imagine you're working at a cafeteria:

๐Ÿฝ Unbuffered I/O:

  • A customer orders 1 french fry.

  • You go to the kitchen, grab 1 fry, walk back, and give it.

  • Then they want another... repeat.

  • You're making 100 trips for 100 fries ๐Ÿ˜ต

๐Ÿงƒ Buffered I/O:

  • You wait until they ask for a full plate of fries (say 50).

  • You go once, get a plate, deliver it in one trip.

  • Boom. Less time, less effort. ๐Ÿ’ฏ

This is how buffering works.

Why use Fast I/O?

In CP or large datasets:

  • fmt.Scan() can TLE.

  • bufio.Reader/Writer is ~3xโ€“10x faster.

  • Helps when you're reading/writing millions of numbers or strings.

Programming Example

๐Ÿข Unbuffered Output:

fmt.Println("Hello")  // Makes a syscall to write
fmt.Println("World")  // Another syscall

Every line = syscall = ๐ŸŒ slow when done a LOT.

โšก Buffered Output:

out := bufio.NewWriter(os.Stdout)
fmt.Fprintln(out, "Hello")  // Goes into memory
fmt.Fprintln(out, "World")  // Still in memory
out.Flush()                 // Sends both at once ๐Ÿš€

Few key notes:

  • ๐Ÿง  Buffer = memory (slice of bytes), usually on the heap

  • ๐Ÿ“ฆ bufio.Writer lives in heap, accessed via variable out

  • โฑ Buffered output is delayed โ†’ flushed via Flush() (can be automatic with defer)

  • โš™๏ธ Allocation happens at runtime, not during compilation

Question that can be asked!

"Isn't heap slower than stack? Then why is buffered I/O faster?"

Answer:

  • โœ… Stack is faster than heap.
  • โŒ But stack can't hold large/dynamic buffers.
  • ๐Ÿ”ฅ Buffered I/O is fast not because of where the buffer is, but because it minimizes syscalls by batching output.
  • ๐Ÿ’ก One syscall is always better than many, even if the buffer lives on the heap.

๐Ÿค– Analogy:

  • ๐Ÿข Unbuffered I/O = 1 pigeon per letter = slow

  • ๐Ÿš€ Buffered I/O = write all letters, put them in a bag, and send 1 pigeon with the whole bag

The cost isn't the writing... it's the pigeon trips (syscalls).

Experiment001

BAD VERSION (missing output due to early return)

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	out := bufio.NewWriter(os.Stdout)

	fmt.Fprintln(out, "Starting program...")

	var condition = true
	if condition {
		fmt.Fprintln(out, "Early exit happened!")
		return // โš ๏ธ Exits before Flush()!
	}

	fmt.Fprintln(out, "Program finished normally.")
	out.Flush()
}

What happens?

  • "Starting program..." and "Early exit happened!" get written to the buffer.

  • But we never call Flush(), because return happens before it.

  • So... you see nothing. Total ghosted output.

GOOD VERSION using defer out.Flush()

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	out := bufio.NewWriter(os.Stdout)
	defer out.Flush() // ๐Ÿ‘ˆ now this always runs at the end, no matter where return happens

	fmt.Fprintln(out, "Starting program...")

	var condition = true
	if condition {
		fmt.Fprintln(out, "Early exit happened!")
		return // ๐Ÿ›ก๏ธ still prints everything because defer handles it
	}

	fmt.Fprintln(out, "Program finished normally.")
}

Now everything you printed gets flushed properly, even if your function returns early

๐Ÿง  TL;DR

  • You must use defer out.Flush() when:

    • Your function has multiple return paths.

    • You want to avoid forgetting to flush manually.

  • It's basically an "auto-save" for your output.

Experiment002

package main

import (
	"bufio"
	"fmt"
	"os"
	"time"
)

func main() {
	out := bufio.NewWriter(os.Stdout)

	fmt.Fprintln(out, "Starting tests...")
	out.Flush() // ๐Ÿ‘ˆ First flush so this shows immediately

	for i := 1; i <= 5; i++ {
		time.Sleep(500 * time.Millisecond) // simulate work
		fmt.Fprintf(out, "Testcase %d passed โœ…\n", i)
		out.Flush() // ๐Ÿ‘ˆ Flush after every update to give real-time output
	}

	fmt.Fprintln(out, "All tests completed ๐ŸŽ‰")
	out.Flush() // ๐Ÿ‘ˆ Final flush just in case
}

๐Ÿ’ก Why multiple Flush()s?

Because if you don't flush after each message, everything gets buffered and shows up only at the end, defeating the point of real-time feedback.

๐Ÿ”ฅ Use case examples:

  • Online judges showing output before TLE

  • CLIs with progress logs

  • Live coding tools or debuggers

  • Streaming JSON or logs over network

Experiment003

The point of this experiment is to see how much time difference is there between buffered I/O and unbuffered I/O

โœ… Buffered I/O Version (with timing)

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

var (
	in  = bufio.NewReader(os.Stdin)
	out = bufio.NewWriter(os.Stdout)
)

func readLine() string {
	line, _ := in.ReadString('\n')
	return strings.TrimSpace(line)
}

func readInt() int {
	n, _ := strconv.Atoi(readLine())
	return n
}

func readInts() []int {
	line := readLine()
	parts := strings.Fields(line)
	nums := make([]int, len(parts))
	for i, s := range parts {
		nums[i], _ = strconv.Atoi(s)
	}
	return nums
}

func isPrime(n int) bool {
	if n < 2 {
		return false
	}
	for i := 2; i*i <= n; i++ {
		if n%i == 0 {
			return false
		}
	}
	return true
}

func main() {
	start := time.Now()
	defer out.Flush()

	t := readInt()
	for i := 0; i < t; i++ {
		n := readInt()
		arr := readInts()
		found := false
		for _, v := range arr {
			if isPrime(v) {
				found = true
				break
			}
		}
		fmt.Fprintln(out, found)
	}
	elapsed := time.Since(start)
	fmt.Fprintln(os.Stderr, "Buffered IO Time:", elapsed)
}

๐Ÿข Unbuffered I/O Version (with timing)

package main

import (
	"fmt"
	"os"
	"time"
)

func isPrime(n int) bool {
	if n < 2 {
		return false
	}
	for i := 2; i*i <= n; i++ {
		if n%i == 0 {
			return false
		}
	}
	return true
}

func main() {
	start := time.Now()

	var t int
	fmt.Scan(&t)

	for i := 0; i < t; i++ {
		var n int
		fmt.Scan(&n)
		found := false
		for j := 0; j < n; j++ {
			var x int
			fmt.Scan(&x)
			if isPrime(x) {
				found = true
			}
		}
		fmt.Println(found)
	}

	elapsed := time.Since(start)
	fmt.Fprintln(os.Stderr, "Unbuffered IO Time:", elapsed)
}

How to Test:

Run both programs with the same input. Example input file (input.txt):

5
4
1 4 6 8
3
9 11 10
5
1 2 3 4 5
2
10 15
3
6 7 8

Then run:

go run buffered.go < input.txt
go run unbuffered.go < input.txt

Now, fastIO template (with full breakdown)

package main

import (
	"bufio"           // For fast buffered input/output
	"fmt"             // For formatted printing (we only use fmt for testing/debug)
	"os"              // To get access to stdin, stdout
	"strconv"         // To convert strings to ints
	"strings"         // To split strings into slices (fields)
)

//Globals

var (
	in  = bufio.NewReader(os.Stdin)   // Fast input reader
	out = bufio.NewWriter(os.Stdout)  // Fast output writer
)

/*
bufio.NewReader: Buffers stdin so you donโ€™t block on every byte.

bufio.NewWriter: Buffers stdout, much faster. Needs out.Flush() at the end.
*/

func readLine() string {
	line, _ := in.ReadString('\n')               // Reads until newline
	return strings.TrimSpace(line)               // Removes \n and spaces
}

/*
Returns one full line as string.

TrimSpace avoids issues with trailing \n or spaces.
*/

func readInt() int {
	n, _ := strconv.Atoi(readLine())            // Converts line to int
	return n
}

/*
    Calls readLine() โ†’ "123"

    Converts to 123

    Ignores errors (_) โ€” fine for CP, but not ideal in prod code.
*/
func readInts() []int {
	line := readLine()                           // Reads a line like: "1 2 3"
	parts := strings.Fields(line)                // Splits into ["1", "2", "3"]
	nums := make([]int, len(parts))
	for i, s := range parts {
		nums[i], _ = strconv.Atoi(s)             // Convert each to int
	}
	return nums
}
//Used for reading multiple ints in one line, space-separated.

func readStrings() []string {
	return strings.Fields(readLine())            // Like readInts, but keeps strings
}

/*
- Splits a line into words by space.

- Perfect for string array input.
*/

func main() {
    defer out.Flush() //Ensures everything buffered in out is printed at the end.


    //code goes here
}

SQL Jounrey from 0 to 1

So, I started learning sql and databases for my job prep. But in truth, job prep is just a trigger, I wanted to and would have learn databases anyway as I am working on projects where I have to collect and keep user data and keep them secure. So, far I have learned only the basics. I already knew about CRUD but here I will apply it and once I get confident in some of it I will make a detailed path of how everyone should proceed. Using MySQL for now as I am following a tutorial but will soon use PostgreSQL

Section and project structure update

bytebook/
โ”œโ”€โ”€ golang/
โ”‚   โ””โ”€โ”€ ...
โ”œโ”€โ”€ sql/
โ”‚   โ”œโ”€โ”€ 00-sql-cheatsheet.md
โ”‚   โ”œโ”€โ”€ 01-select-queries.sql
โ”‚   โ”œโ”€โ”€ 02-joins-explained.md
โ”‚   โ”œโ”€โ”€ 03-sqlbolt-solutions/
โ”‚   โ”‚   โ”œโ”€โ”€ sqlbolt-01-select.png
โ”‚   โ”‚   โ”œโ”€โ”€ sqlbolt-02-join.png
โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”œโ”€โ”€ 04-leetcode-solutions/
โ”‚   โ”‚   โ”œโ”€โ”€ easy/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ 175-combine-two-tables.sql
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”‚   โ””โ”€โ”€ medium/
โ”‚   โ”‚       โ””โ”€โ”€ ...
โ”‚   โ”œโ”€โ”€ flashcards.md
โ”‚   โ””โ”€โ”€ README.md

The thing that I think will be super helpful to learn

  1. I will be creating a cheatsheet kind of thing for quick overview of sql commands and stuff and call it the SQL cheesheet.

  2. I will also be creating a flashcards collection(kind of like screenshot of the code but it can be done in .md file too)

  3. Will solve and practice sql related problems from leetcode and sqlbolt and use screenshot strategy on that so that it looks cool.

๐Ÿ–ผ Screenshot Strategy

For each screenshot:

  1. Take a clear pic of the solution from SQLBolt or LeetCode

  2. Rename descriptively: sqlbolt-03-select-where.png

  3. Add captions in a .md file:

### SQLBolt #3 โ€” SELECT WHERE
![SQLBolt-3](./sqlbolt-03-select-where.png)
Used WHERE to filter based on age. Simple but foundational.

๐Ÿ—ƒ Flashcards Strategy

Make a flashcards.md that uses a format compatible with Anki or can be quickly skimmed:

### What does LEFT JOIN do?
Returns all records from the left table, and the matched records from the right table. NULLs if no match.

### Difference between WHERE and HAVING?
WHERE filters rows before grouping. HAVING filters groups after GROUP BY.

### Syntax for subquery?
SELECT * FROM (SELECT id FROM users WHERE active = 1) AS active_users;

๐Ÿง  SQL Cheatsheet (In Progress...)

๐Ÿ“Œ What is a Database?

  • A database is a collection of related information or data that can be stored, managed, and retrieved.

๐Ÿ“Œ SQL vs NoSQL

  • SQL: Structured Query Language used to manage Relational Databases (RDBMS).
  • NoSQL: Used for Non-Relational Databases (NRDBMS) like MongoDB, Redis, etc.

๐Ÿ“Œ Arch Linux Tip

  • In Arch, MariaDB is a drop-in replacement for MySQL.

๐Ÿ— Table Creation & Management

โœ… CREATE TABLE

CREATE TABLE users (
  id INT AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(100) UNIQUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
);

โœ… DESCRIBE

DESCRIBE users;

โœ… ALTER TABLE

  • Add column
ALTER TABLE users ADD age INT;
  • Modify column
ALTER TABLE users MODIFY age DECIMAL(5,2);
  • Drop column
ALTER TABLE users DROP COLUMN age;

โœ… DROP TABLE

DROP TABLE users;

๐Ÿ”‘ Keys in SQL

๐Ÿ”น Primary Key

A column (or set of columns) that uniquely identifies each row in a table.

  • Must be unique
  • Cannot be NULL
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(100)
);

๐Ÿ”น Foreign Key

A column that references the primary key of another table to establish a relationship between them.

  • Maintains referential integrity
  • Can be NULL (if optional relationship)
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  FOREIGN KEY (user_id) REFERENCES users(id)
);

๐Ÿ”น Surrogate Key

An artificial key used as a unique identifier (usually AUTO_INCREMENT or UUID)

  • Has no business meaning
  • Used instead of natural keys to avoid complications
CREATE TABLE products (
  product_id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100)
);

๐Ÿ”น Composite Key

A primary key made of multiple columns. Used when a single column canโ€™t uniquely identify a row.

CREATE TABLE enrollment (
  student_id INT,
  course_id INT,
  PRIMARY KEY (student_id, course_id)
);

๐Ÿ”น Candidate Key

Any column (or set of columns) that could be a primary key (i.e., unique and non-null).

  • One becomes the primary key, others are alternate keys.

Alternate Key

A candidate key that was not chosen as the primary key but can still uniquely identify rows.

๐Ÿ“‹ Data Types

TypeDescription
INTInteger values
DECIMALFixed-point decimal (money, etc.)
VARCHARVariable-length text
BLOBBinary large object (images, files)
DATEStores year-month-day
TIMESTAMPStores date & time

๐Ÿ” Querying & Data Insertion

โœ… SELECT

SELECT * FROM users;

โœ… INSERT

INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');

๐Ÿ”’ Constraints

ConstraintUse Case
NOT NULLPrevents null entries in a column
UNIQUEEnsures unique value in a column
DEFAULTSets default value if none is provided
AUTO_INCREMENTAutomatically increments numeric value

๐Ÿ“ UPDATE & DELETE in SQL (MySQL)

๐Ÿ”„ UPDATE โ€“ Modify Existing Records

UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;

โœ… Example:

-- Update a student's name where ID is 1
UPDATE students
SET name = 'Tanvir Ahmed'
WHERE id = 1;

โš ๏ธ Always use WHERE with UPDATE to avoid updating all rows!


โŒ DELETE โ€“ Remove Records from a Table

DELETE FROM table_name
WHERE condition;

โœ… Example:

-- Delete a student where ID is 3
DELETE FROM students
WHERE id = 3;

โš ๏ธ DELETE without a WHERE clause removes all rows! Use carefully.


๐Ÿงช Pro Tips

  • ๐Ÿ”’ Always SELECT the rows first before doing an UPDATE or DELETE:
    SELECT * FROM students WHERE id = 1;
    
  • ๐Ÿงฏ Use transactions if you're unsure:
    START TRANSACTION;
    UPDATE students SET name = 'Test' WHERE id = 1;
    ROLLBACK; -- or COMMIT;
    

๐Ÿ” Basic Queries

  • Retrieve all columns: SELECT * FROM table_name;
  • Retrieve specific columns: SELECT name, age FROM users;
  • Filtering: SELECT * FROM users WHERE age > 18;
  • Sorting: SELECT * FROM users ORDER BY name ASC;
  • Limiting: SELECT * FROM users LIMIT 5;

๐Ÿงฑ Creating a Simple Schema

CREATE TABLE students (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    age INT,
    email VARCHAR(100) UNIQUE
);

๐Ÿงฎ Functions & Aggregates

  • COUNT(*) โ€“ total rows
  • AVG(column) โ€“ average
  • SUM(column) โ€“ total sum
  • MAX(column) / MIN(column) โ€“ max/min values
  • NOW() โ€“ current datetime
SELECT COUNT(*) FROM users;
SELECT AVG(age) FROM students;

๐Ÿ”— JOINS

  • INNER JOIN: Only matching rows
SELECT * FROM orders
INNER JOIN customers ON orders.customer_id = customers.id;
  • LEFT JOIN: All from left + matched from right
  • RIGHT JOIN: All from right + matched from left

๐Ÿฅš Nested Queries

SELECT name FROM students
WHERE id IN (
    SELECT student_id FROM enrollments WHERE course_id = 1
);

๐Ÿงฌ UNION

  • Combine result sets (must have same number of columns)
SELECT name FROM teachers
UNION
SELECT name FROM students;

๐ŸŒŸ Wildcards

  • % = any number of characters
  • _ = a single character
SELECT * FROM users WHERE name LIKE 'A%';
SELECT * FROM products WHERE code LIKE '_23%';

๐Ÿ” ON DELETE SET NULL / CASCADE

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE SET NULL
);

-- or
ON DELETE CASCADE
-- deletes all related orders when user is deleted

๐Ÿ“˜ Advanced SQL & Database Design Concepts

๐Ÿงฉ DELIMITER in SQL

By default, SQL statements end with a semicolon ;. But when writing complex statements like stored procedures or triggers, we temporarily change the delimiter to avoid premature termination.

๐Ÿ”ง Usage:

DELIMITER $$

CREATE PROCEDURE example_proc()
BEGIN
    -- Multiple SQL statements
    SELECT "Hello";
    SELECT "World";
END $$

DELIMITER ;

DELIMITER $$ tells the MySQL interpreter to treat $$ as the end of the command block instead of ;. This is useful for functions, procedures, and triggers.


โšก Triggers

Triggers are special stored procedures that automatically run in response to specific events on a table.

๐Ÿง  Types of Triggers:

  • BEFORE INSERT
  • AFTER INSERT
  • BEFORE UPDATE
  • AFTER UPDATE
  • BEFORE DELETE
  • AFTER DELETE

โœ… Syntax:

DELIMITER $$

CREATE TRIGGER before_insert_user
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
    SET NEW.created_at = NOW();
END $$

DELIMITER ;

NEW and OLD are special keywords to access values during trigger execution.


๐Ÿงฌ ER Diagrams (Entity-Relationship Diagrams)

ER diagrams visually represent how entities (tables) relate to one another in a database.

๐Ÿงฑ Key Concepts:

๐Ÿงพ Entities:

  • Represent real-world objects (e.g., Student, Course).
  • Become tables in the database.

๐Ÿ”‘ Attributes:

  • Properties of an entity (e.g., name, age, ID).
  • Types:
    • Simple Attribute: Cannot be divided (e.g., age).
    • Composite Attribute: Can be divided (e.g., name โ†’ first, last).
    • Derived Attribute: Computed (e.g., age from DOB).
    • Multivalued Attribute: Has multiple values (e.g., phone numbers).

๐Ÿ”— Relationships:

  • Connects two or more entities.
  • Cardinality types:
    • One-to-One
    • One-to-Many
    • Many-to-Many

๐Ÿงฉ Keys:

  • Primary Key: Uniquely identifies a record.
  • Foreign Key: Creates a link between tables.

๐Ÿ”„ Participation:

  • Total Participation: Every entity must participate in a relationship.
  • Partial Participation: Optional participation.

๐Ÿ”– Example ER Scenario:

Entities: Student, Course, Enrollment
Relations:

  • A Student can enroll in multiple Courses.
  • A Course can have many Students.

This forms a many-to-many relationship, usually resolved with an intermediate Enrollment table.


ER diagrams help with planning, normalization, and identifying relationships before creating the schema.

๐Ÿง  SQL Flashcards โ€” CLI Edition

Use these to review quickly in a terminal-like format. Keep adding as you level up!


๐Ÿ—‚๏ธ  Question:
What is a database?

๐Ÿ“ฆ  Answer:
A collection of related information that can be stored, queried, and managed.

๐Ÿ—‚๏ธ  Question:
What is SQL?

๐Ÿ“ฆ  Answer:
A language used to manage and query relational databases (RDBMS).

๐Ÿ—‚๏ธ  Question:
What is NoSQL?

๐Ÿ“ฆ  Answer:
A category of non-relational databases used for unstructured or semi-structured data.

๐Ÿ—‚๏ธ  Question:
What is the MySQL equivalent on Arch Linux?

๐Ÿ“ฆ  Answer:
MariaDB is a drop-in replacement for MySQL on Arch Linux.

๐Ÿ—‚๏ธ  Question:
What is an RDBMS?

๐Ÿ“ฆ  Answer:
Relational Database Management System โ€” stores data in tables with relationships.

๐Ÿ—‚๏ธ  Question:
What is an NRDBMS?

๐Ÿ“ฆ  Answer:
Non-relational Database Management System โ€” stores data as documents, key-values, or graphs.

๐Ÿ—‚๏ธ  Question:
How do you create a new table?

๐Ÿ“ฆ  Answer:
Use the CREATE TABLE statement with column names, types, and constraints.
Example:
CREATE TABLE users (id INT, name VARCHAR(100));

๐Ÿ—‚๏ธ  Question:
How do you see the structure of a table?

๐Ÿ“ฆ  Answer:
Use: DESCRIBE table_name;

๐Ÿ—‚๏ธ  Question:
How do you add a column to an existing table?

๐Ÿ“ฆ  Answer:
ALTER TABLE table_name ADD column_name datatype;

๐Ÿ—‚๏ธ  Question:
How do you modify a column's datatype?

๐Ÿ“ฆ  Answer:
ALTER TABLE table_name MODIFY column_name new_datatype;

๐Ÿ—‚๏ธ  Question:
How do you delete a column from a table?

๐Ÿ“ฆ  Answer:
ALTER TABLE table_name DROP COLUMN column_name;

๐Ÿ—‚๏ธ  Question:
How do you delete a table?

๐Ÿ“ฆ  Answer:
DROP TABLE table_name;

๐Ÿ—‚๏ธ  Question:
How do you view all data from a table?

๐Ÿ“ฆ  Answer:
SELECT * FROM table_name;

๐Ÿ—‚๏ธ  Question:
How do you insert data into a table?

๐Ÿ“ฆ  Answer:
INSERT INTO table_name (col1, col2) VALUES (val1, val2);

๐Ÿ—‚๏ธ  Question:
What does NOT NULL do?

๐Ÿ“ฆ  Answer:
Ensures a column cannot have NULL values.

๐Ÿ—‚๏ธ  Question:
What does UNIQUE do?

๐Ÿ“ฆ  Answer:
Ensures all values in a column are unique.

๐Ÿ—‚๏ธ  Question:
What does DEFAULT do?

๐Ÿ“ฆ  Answer:
Sets a default value for a column if no value is provided on insert.

๐Ÿ—‚๏ธ  Question:
What does AUTO_INCREMENT do?

๐Ÿ“ฆ  Answer:
Automatically increases the value of a numeric column (usually a primary key) on each new insert.

๐Ÿ—‚๏ธ  Question:
What is a Primary Key?

๐Ÿ“ฆ  Answer:
A column (or combination) that uniquely identifies each row in a table. Cannot be NULL or duplicated.

๐Ÿ—‚๏ธ  Question:
What is a Foreign Key?

๐Ÿ“ฆ  Answer:
A column that refers to the Primary Key in another table to maintain relational integrity.

๐Ÿ—‚๏ธ  Question:
What is a Surrogate Key?

๐Ÿ“ฆ  Answer:
An artificially generated key (like AUTO_INCREMENT or UUID) used to uniquely identify a record without business meaning.

๐Ÿ—‚๏ธ  Question:
What is a Composite Key?

๐Ÿ“ฆ  Answer:
A Primary Key made up of multiple columns together to uniquely identify a row.

๐Ÿ—‚๏ธ  Question:
What is a Candidate Key?

๐Ÿ“ฆ  Answer:
A column (or group) that could serve as a Primary Key โ€” it's unique and non-null.

๐Ÿ—‚๏ธ  Question:
What is an Alternate Key?

๐Ÿ“ฆ  Answer:
A Candidate Key that was not chosen as the Primary Key but is still unique and can identify rows.

๐Ÿ—‚๏ธ  Question:
What SQL statement is used to modify existing records in a table?

๐Ÿ“ฆ  Answer:
UPDATE

๐Ÿ—‚๏ธ  Question:
What clause must you include with UPDATE to avoid changing all rows?

๐Ÿ“ฆ  Answer:
WHERE clause

๐Ÿ—‚๏ธ  Question:
What happens if you run UPDATE students SET name = 'Tanvir'; without WHERE?

๐Ÿ“ฆ  Answer:
It updates the name field for all rows in the students table.

๐Ÿ—‚๏ธ  Question:
What's the basic syntax of the UPDATE statement?

๐Ÿ“ฆ  Answer:
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;

๐Ÿ—‚๏ธ  Question:
How do you remove records from a table in SQL?

๐Ÿ“ฆ  Answer:
Using the DELETE statement

๐Ÿ—‚๏ธ  Question:
Write a SQL query to delete a student whose id is 5.

๐Ÿ“ฆ  Answer:
DELETE FROM students
WHERE id = 5;

๐Ÿ—‚๏ธ  Question:
What happens if you run DELETE FROM students; without a WHERE clause?

๐Ÿ“ฆ  Answer:
It deletes all rows from the students table.

๐Ÿ—‚๏ธ  Question:
Which SQL keyword starts a transaction to safely test updates or deletes?

๐Ÿ“ฆ  Answer:
START TRANSACTION

๐Ÿ—‚๏ธ  Question:
What SQL command undoes changes made during a transaction?

๐Ÿ“ฆ  Answer:
ROLLBACK

๐Ÿ—‚๏ธ  Question:
Whatโ€™s the safest practice before running UPDATE or DELETE?

๐Ÿ“ฆ  Answer:
Run a SELECT with the same WHERE clause to preview affected rows.

๐Ÿ—‚๏ธ  Question:
What does the wildcard % do in SQL?

๐Ÿ“ฆ  Answer:
Matches zero or more characters in a LIKE pattern.
Example: 'A%' matches anything starting with A.

๐Ÿ—‚๏ธ  Question:
Whatโ€™s the purpose of UNION in SQL?

๐Ÿ“ฆ  Answer:
Combines result sets from two SELECT queries into one, removing duplicates.

๐Ÿ—‚๏ธ  Question:
How does an INNER JOIN work?

๐Ÿ“ฆ  Answer:
Returns only the rows where there is a match in both joined tables.

๐Ÿ—‚๏ธ  Question:
Whatโ€™s the difference between LEFT JOIN and RIGHT JOIN?

๐Ÿ“ฆ  Answer:
LEFT JOIN returns all rows from the left table and matched rows from the right.
RIGHT JOIN does the opposite.

๐Ÿ—‚๏ธ  Question:
What does ON DELETE SET NULL do?

๐Ÿ“ฆ  Answer:
When a referenced row is deleted, it sets the foreign key column to NULL.

๐Ÿ—‚๏ธ  Question:
What does ON DELETE CASCADE do?

๐Ÿ“ฆ  Answer:
Automatically deletes rows in child table when the referenced row in parent table is deleted.

๐Ÿ—‚๏ธ  Question:
Whatโ€™s a nested query (subquery)?

๐Ÿ“ฆ  Answer:
A query within another query, often used to filter or compute dynamic conditions.

๐Ÿ—‚๏ธ  Question:
What does COUNT(*) do in SQL?

๐Ÿ“ฆ  Answer:
Returns the total number of rows in a table.

๐Ÿ—‚๏ธ  Question:
How do you sort query results?

๐Ÿ“ฆ  Answer:
Using ORDER BY clause: ORDER BY column ASC/DESC;

๐Ÿ—‚๏ธ  Question:
How do you limit the number of rows in a result?

๐Ÿ“ฆ  Answer:
Using the LIMIT clause. Example: SELECT * FROM users LIMIT 10;

๐Ÿ‘พ Keep these in mind when designing tables or answering DBMS interview questions.