๐ 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
- ๐ View Golang Notes and Experiments
- Basics, idioms, concurrency, and project patterns
๐๏ธ 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 thebytebook
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
- Download the installer from: https://go.dev/dl
- Run the installer and follow the prompts.
- 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
-
Same Folder = Same Package All
.go
files in the same directory should have the same package name (main
if you want to run them). -
Running Multiple Files You must include all necessary files when using
go run
, like:go run main.go add.go
-
Initializing a New Module Start with:
go mod init <module_name>
-
Managing Dependencies Use:
go get <package_name> go mod tidy
-
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
-
โ 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. -
๐ค 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.
-
โ๏ธ 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:
-
๐ Variable shadowing occurs when a local variable has the same name as a variable in an outer scope.
-
โ Go won't throw an error โ itโll just use the innermost version in the current scope.
-
๐ฆ Global
a
is untouched and printed outside theif
block. -
โ 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
-
init()
is a special Go function that runs beforemain()
, automatically. -
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
-
-
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
-
โ
init()
always runs beforemain()
even if itโs written aftermain()
in your code. -
โ๏ธ You can use it to initialize configs, connections, default values, etc.
-
๐ก A Go file can have at most one
main()
, but multipleinit()
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
inadd(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) { ... }
- Named function:
3. Higher Order Function
A function that takes a function as a parameter, returns a function, or both.
- Example:
processOperation
takes a functionop
as a parametercall()
returns a functionadd
4. Callback Function
- A function that is passed into another function to be executed later.
- In
processOperation(4, 5, add)
, the functionadd
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
init()
is run automatically beforemain()
โ prints:
Hello
main()
runs and calls: -[]add(5, 4)
โ prints:
-[]9
add(a, 3)
โ usesa = 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
Concept | Explanation |
---|---|
Compilation Phase | Parses and compiles source code into a binary executable. No code runs yet. |
Execution Phase | Runs the compiled binary, starting from init() and then main() . |
Code Segment | Where compiled functions (like main , call , and anonymous functions) live. |
Data Segment | Holds global variables and constants (like p and a ). |
Function Expressions | Treated 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 anonymousadd
function insidecall
- Constants:
- 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 thenmain()
. - 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
Segment | What's Stored |
---|---|
Code Segment | Compiled instructions (functions) |
Data Segment | Global and static variables (a , p ) |
Stack | Local variables (age ) |
Heap | Escaping 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
Term | Meaning |
---|---|
Receiver | The type a method is attached to (e.g., *Todos ) |
Value Receiver | Gets a copy of the value; doesn't affect the original |
Pointer Receiver | Gets 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
- For
๐ Memory Layout Visualization
Example:
arr := [2]int{3, 6}
Memory Layout
Address | Value | Meaning |
---|---|---|
0x1000 | 3 | arr[0] |
0x1004 | 6 | arr[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"}
Index | Value |
---|---|
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
- What is a Slice?
- How many parts does a Slice have?
- How to determine Pointer, Length, and Capacity of a Slice?
- Creating a Slice from an existing Array
- Creating a Slice from an existing Slice
- Slice Literal
- Creating a Slice with
make()
(length only) - Creating a Slice with
make()
(length and capacity) - Creating an Empty or Nil Slice
- Appending elements to a Slice
- What happens internally when appending (Heap and Underlying Array behavior)
- How the underlying array increases dynamically
- Some interesting examples and interview questions
- 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)
โก๏ธ Lengthcap(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 ofarr
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 affectarr
.
โ๏ธ 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 tocap = 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
andcap
are less than 1024. - The 25% growth for slices when the
len
andcap
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
andy
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
- Ignoring errors on purpose (People do this all the time)
- Force the compiler to chill by making the unused imports and variables
_
- Ignoring unused loop vars
- 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()
- Declaring and keeping it for later uses
result := doSomething()
_ = result
//TODO: Use result later
- 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
- Loop variable leaks beacuse of
_
abuse - Blank import to secretly bring hell
- 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
- I will create a big slice (pretend it's a chonk of data)
- Will make two function
- One that takes it by value (makes a full copy)
- One that takes it by reference (just a pointer)
- 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
-
I will be creating a cheatsheet kind of thing for quick overview of sql commands and stuff and call it the
SQL cheesheet
. -
I will also be creating a flashcards collection(kind of like screenshot of the code but it can be done in .md file too)
-
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:
-
Take a clear pic of the solution from SQLBolt or LeetCode
-
Rename descriptively:
sqlbolt-03-select-where.png
-
Add captions in a
.md
file:
### SQLBolt #3 โ SELECT WHERE

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
Type | Description |
---|---|
INT | Integer values |
DECIMAL | Fixed-point decimal (money, etc.) |
VARCHAR | Variable-length text |
BLOB | Binary large object (images, files) |
DATE | Stores year-month-day |
TIMESTAMP | Stores date & time |
๐ Querying & Data Insertion
โ SELECT
SELECT * FROM users;
โ INSERT
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
๐ Constraints
Constraint | Use Case |
---|---|
NOT NULL | Prevents null entries in a column |
UNIQUE | Ensures unique value in a column |
DEFAULT | Sets default value if none is provided |
AUTO_INCREMENT | Automatically 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
withUPDATE
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 aWHERE
clause removes all rows! Use carefully.
๐งช Pro Tips
- ๐ Always
SELECT
the rows first before doing anUPDATE
orDELETE
: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 rowsAVG(column)
โ averageSUM(column)
โ total sumMAX(column)
/MIN(column)
โ max/min valuesNOW()
โ 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
andOLD
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 multipleCourses
. - A
Course
can have manyStudents
.
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.