from the ground up.
Foundations of Computing
Before writing a single line of code, it pays to understand what a computer actually is — and how it came to exist. Computer Science is not merely programming; it is the study of computation itself: what can be computed, how efficiently, and with what resources.
In this module we explore the philosophical and physical roots of computing, the binary number system that underlies all digital information, and the logic gates from which every processor is built.
What is Computer Science?
Computer Science is the disciplined study of algorithms (step-by-step problem-solving procedures), data structures (ways to organize information), and the theoretical limits of what machines can compute. It bridges pure mathematics, electrical engineering, linguistics, and cognitive science.
A useful distinction: programming is a craft — the act of writing instructions for a machine. Computer Science is the underlying science that tells us which instructions are possible, efficient, or even computable at all.
A Brief History
Charles Babbage (1791–1871) designed the first mechanical computer concept — the Analytical Engine. His collaborator Ada Lovelace wrote what is considered the first algorithm, making her the world's first programmer.
Alan Turing (1912–1954) formalized the concept of computation in 1936 with his Turing Machine — an abstract device that defined the limits of what any computer could ever calculate. He also cracked the Nazi Enigma cipher and invented the foundational ideas behind AI.
John von Neumann (1903–1957) designed the architecture almost all modern computers use: a CPU, memory, and input/output devices, where both programs and data live in the same memory space.
The first electronic computer, ENIAC (1945), filled a room and consumed 150 kilowatts of power. Today a smartphone holds billions of times more computing power in a chip smaller than your fingernail.
The Binary Number System
All digital computers store and process information as binary — a base-2 number system using only two symbols: 0 and 1. Why? Because transistors (the fundamental component of all chips) can be either off (0) or on (1), making binary a perfect physical representation.
A single binary digit is called a bit. Eight bits form a byte, which can represent 256 different values (2⁸ = 256).
// Decimal → Binary conversion
// Each position is a power of 2 (right to left)
// Position: 8 4 2 1 (powers of 2)
// Binary: 0 0 0 0 = 0 in decimal
// Binary: 0 0 0 1 = 1 in decimal
// Binary: 0 0 1 0 = 2 in decimal
// Binary: 0 0 1 1 = 3 in decimal
// Binary: 0 1 0 1 = 5 in decimal
// Binary: 1 0 0 1 = 9 in decimal
// Binary: 1 1 1 1 = 15 in decimal
// In JavaScript:
let decimal = 42;
let binary = decimal.toString(2); // "101010"
let back = parseInt("101010", 2); // 42
Computers use binary because electrical signals are either present (1) or absent (0). Representing more digits would require distinguishing finer voltage levels — prone to errors and noise. Binary is robust and physically natural.
Boolean Logic
Boolean algebra, developed by George Boole in 1854, is the mathematics of true/false values. It is the direct foundation of digital circuits. Every computation ever performed by a computer is ultimately a combination of three operations: AND, OR, and NOT.
| A | B | A AND B | A OR B | NOT A | A XOR B |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 | 0 |
| 0 | 1 | 0 | 1 | 1 | 1 |
| 1 | 0 | 0 | 1 | 0 | 1 |
| 1 | 1 | 1 | 1 | 0 | 0 |
Physical logic gates implement these operations using transistors. Millions of such gates form an Arithmetic Logic Unit (ALU), which performs all the arithmetic and comparisons inside a CPU.
How a Computer Works
A modern computer follows the Von Neumann architecture: instructions and data share the same memory, and the CPU repeatedly fetches, decodes, and executes instructions in a cycle.
✅ Key Takeaways
- Computer Science studies computation — algorithms, data, and theory — not just programming.
- Computers represent all data as binary (base-2) numbers using bits and bytes.
- Boolean logic (AND, OR, NOT) is the mathematical foundation of all digital circuits.
- Modern computers use the Von Neumann architecture: CPU + shared memory for code and data.
- Alan Turing defined the theoretical limits of computation before physical computers existed.
Programming Fundamentals
Programming is the art of expressing solutions to problems in a language that a computer can execute. Before learning any specific language deeply, understanding the universal building blocks of programming will make you fluent in any language far faster.
This module teaches variables, control flow, functions, and scope using JavaScript as the demonstration language — though every concept applies in Python, Java, C, and beyond.
Variables & Data Types
A variable is a named container that holds a value in memory. Every piece of data a program works with — a number, a name, a flag — lives in a variable. Data types define what kind of value a variable holds.
// Primitive data types
let age = 25; // Number (integer)
let price = 9.99; // Number (float)
let name = "Alice"; // String
let isLoggedIn = true; // Boolean
let nothing = null; // Null (intentionally empty)
let notDefined; // undefined (declared, not assigned)
// const = cannot be reassigned; let = can be reassigned
const PI = 3.14159;
PI = 3; // ❌ TypeError: Assignment to constant variable
// Type checking
typeof "hello" // "string"
typeof 42 // "number"
typeof true // "boolean"
Operators & Expressions
Operators combine values to produce new values. An expression is any combination of values, variables, and operators that evaluates to a single result.
// Arithmetic operators
10 + 3 // 13 (addition)
10 - 3 // 7 (subtraction)
10 * 3 // 30 (multiplication)
10 / 3 // 3.33 (division)
10 % 3 // 1 (modulus — remainder)
2 ** 8 // 256 (exponentiation)
// Comparison operators (return boolean)
5 === 5 // true (strict equality)
5 !== 6 // true (strict inequality)
5 > 3 // true
5 <= 5 // true
// Logical operators
true && false // false (AND)
true || false // true (OR)
!true // false (NOT)
Control Flow
Programs rarely execute line by line from top to bottom. Control flow statements let you make decisions and repeat actions — the foundation of all non-trivial logic.
let score = 78;
let grade;
if (score >= 90) {
grade = "A";
} else if (score >= 80) {
grade = "B";
} else if (score >= 70) {
grade = "C"; // ← this branch executes
} else {
grade = "F";
}
// Ternary operator (inline if)
let pass = score >= 60 ? "Pass" : "Fail"; // "Pass"
// for loop: run a set number of times
for (let i = 0; i < 5; i++) {
console.log(i); // prints 0 1 2 3 4
}
// while loop: run while condition is true
let count = 0;
while (count < 3) {
console.log("Tick" + count);
count++;
}
// for…of: iterate over an array
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit); // apple, banana, cherry
}
Functions
A function is a reusable, named block of code that performs a specific task. Functions accept parameters (inputs) and may return a value (output). They are the primary tool for organizing code and avoiding repetition.
// Function declaration
function add(a, b) {
return a + b;
}
add(3, 4); // 7
// Arrow function (concise syntax)
const multiply = (a, b) => a * b;
multiply(3, 4); // 12
// Default parameters
function greet(name = "World") {
return `Hello, ${name}!`;
}
greet(); // "Hello, World!"
greet("Alice"); // "Hello, Alice!"
// Higher-order function (takes function as argument)
const nums = [1, 2, 3, 4, 5];
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
const doubled = nums.map(n => n * 2); // [2,4,6,8,10]
Scope & the Call Stack
Scope determines where in your code a variable is accessible. Variables declared inside a function are local to that function. Variables declared outside any function are global. Understanding scope prevents subtle bugs where variables unexpectedly overwrite each other.
The call stack is a data structure the runtime uses to track which function is currently running. When you call a function, it is pushed onto the stack. When it returns, it is popped off. A stack overflow occurs when the stack grows too deep — usually from infinite recursion.
✅ Key Takeaways
- Variables hold typed values; prefer const unless reassignment is needed.
- Control flow (if/else, loops) lets programs make decisions and repeat work.
- Functions are reusable blocks; they take inputs (parameters) and return outputs.
- Scope controls variable visibility — local variables are isolated inside functions.
- The call stack tracks active function calls; deep recursion can overflow it.
Data Structures
Data structures are the backbone of efficient software. Choosing the right structure can be the difference between a program that runs in milliseconds and one that takes hours. Every major algorithm assumes a particular data structure, so mastering these is essential before tackling algorithms.
Arrays
An array (or list) is the simplest and most universally used data structure. It stores elements in contiguous memory at indexed positions, enabling O(1) random access — you can jump to any element instantly by its index.
const arr = [10, 20, 30, 40];
arr[0]; // 10 — O(1) access
arr.push(50); // add to end — O(1)
arr.pop(); // remove from end — O(1)
arr.shift(); // remove from front — O(n) shifts all elements
arr.splice(1,1); // remove at index 1 — O(n)
arr.length; // 4
// 2D array (matrix)
const matrix = [[1,2],[3,4],[5,6]];
matrix[1][0]; // 3
Linked Lists
A linked list is a chain of nodes, each holding a value and a reference (pointer) to the next node. Unlike arrays, nodes need not be adjacent in memory — making insertions and deletions at arbitrary positions O(1) — but random access is O(n) because you must traverse the chain.
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
class LinkedList {
constructor() { this.head = null; }
append(val) {
const node = new Node(val);
if (!this.head) { this.head = node; return; }
let cur = this.head;
while (cur.next) cur = cur.next;
cur.next = node;
}
}
// head → [1] → [2] → [3] → null
const list = new LinkedList();
[1,2,3].forEach(v => list.append(v));
Stacks & Queues
A stack enforces Last In, First Out (LIFO) order — like a stack of plates; you always add and remove from the top. Used in function call stacks, undo operations, and expression evaluation.
A queue enforces First In, First Out (FIFO) order — like a checkout line. Used in task scheduling, breadth-first search, and message buffers.
// Stack (using array)
const stack = [];
stack.push(1); // [1]
stack.push(2); // [1, 2]
stack.push(3); // [1, 2, 3]
stack.pop(); // 3 ← last in, first out
stack.pop(); // 2
// Queue (using array — shift is O(n); use deque for O(1))
const queue = [];
queue.push("A"); // enqueue
queue.push("B");
queue.push("C");
queue.shift(); // "A" ← first in, first out
queue.shift(); // "B"
Hash Tables (Maps)
A hash table maps keys to values using a hash function that converts a key into a numeric index. This enables average O(1) lookup, insertion, and deletion — making it one of the most powerful and widely used data structures in existence.
const map = new Map();
map.set("alice", 95);
map.set("bob", 87);
map.get("alice"); // 95 — O(1)
map.has("carol"); // false
map.delete("bob");
// Classic use-case: counting frequencies
const freq = (arr) => {
const counts = {};
for (const x of arr)
counts[x] = (counts[x] || 0) + 1;
return counts;
};
freq(["a","b","a","c","a"]); // {a:3, b:1, c:1}
Trees & Graphs
A tree is a hierarchical structure of nodes. Each node has a parent (except the root) and zero or more children. The most common is the Binary Search Tree (BST), where every left child < parent < right child, enabling O(log n) search.
A graph is the most general structure: a set of nodes (vertices) connected by edges. Trees are a special case of graphs. Graphs model networks, social connections, maps, and more.
class BSTNode {
constructor(val) {
this.val = val;
this.left = this.right = null;
}
}
function insert(root, val) {
if (!root) return new BSTNode(val);
if (val < root.val) root.left = insert(root.left, val);
else root.right = insert(root.right, val);
return root;
}
// Build a BST from [5, 3, 7, 1, 4]
// 5
// / \
// 3 7
// / \
// 1 4
| Structure | Access | Search | Insert | Delete | Best For |
|---|---|---|---|---|---|
| Array | O(1) | O(n) | O(n) | O(n) | Random access, iteration |
| Linked List | O(n) | O(n) | O(1) | O(1) | Frequent insertions |
| Stack | O(n) | O(n) | O(1) | O(1) | LIFO operations |
| Hash Table | — | O(1) avg | O(1) avg | O(1) avg | Key-value lookup |
| BST | O(log n) | O(log n) | O(log n) | O(log n) | Sorted data, range queries |
✅ Key Takeaways
- Arrays provide O(1) access by index but O(n) insertion/deletion in the middle.
- Linked lists allow O(1) insertion/deletion at any point, but O(n) access.
- Hash tables are the most powerful general-purpose structure for O(1) key lookup.
- Trees model hierarchical data; BSTs keep sorted order for efficient search.
- Choosing the right data structure is often more impactful than optimizing algorithms.
Algorithms & Complexity
An algorithm is a precise, finite sequence of instructions to solve a problem. The study of algorithms asks: does this work? (correctness), how fast is it? (time complexity), and how much memory does it need? (space complexity).
Big O Notation
Big O notation describes how an algorithm's performance scales with input size n. It measures the worst-case growth rate, discarding constants and lower-order terms. It is the universal language for comparing algorithm efficiency.
O(n²) means: if you double the input, the runtime roughly quadruples. For n = 1,000 elements, an O(n²) algorithm does ~1,000,000 operations; O(n log n) does ~10,000. At scale, this difference is enormous.
Sorting Algorithms
Sorting is one of the most studied problems in CS. Understanding how different sorts work builds intuition for trade-offs between simplicity and performance.
function bubbleSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j+1]) {
[arr[j], arr[j+1]] = [arr[j+1], arr[j]]; // swap
}
}
}
return arr;
}
// Each pass "bubbles" the largest unsorted element to the end
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
function merge(a, b) {
const res = [];
let i = 0, j = 0;
while (i < a.length && j < b.length)
res.push(a[i] <= b[j] ? a[i++] : b[j++]);
return res.concat(a.slice(i), b.slice(j));
}
// Divide-and-conquer: split → sort halves → merge back
Searching Algorithms
// REQUIRES: sorted array
function binarySearch(arr, target) {
let lo = 0, hi = arr.length - 1;
while (lo <= hi) {
const mid = Math.floor((lo + hi) / 2);
if (arr[mid] === target) return mid; // found
if (arr[mid] < target) lo = mid + 1; // search right half
else hi = mid - 1; // search left half
}
return -1; // not found
}
// On 1,000,000 elements: linear = 1M checks; binary = ~20 checks
Recursion & Dynamic Programming
Recursion is when a function calls itself on a smaller sub-problem. Every recursive solution needs a base case (the stopping condition) and a recursive case.
Dynamic programming (DP) solves problems by breaking them into overlapping sub-problems and memoizing (caching) results to avoid redundant computation. It transforms exponential algorithms into polynomial ones.
// NAIVE — O(2ⁿ): redundantly recomputes sub-problems
function fibSlow(n) {
if (n <= 1) return n;
return fibSlow(n-1) + fibSlow(n-2);
}
// MEMOIZED — O(n): cache each result once
const fibFast = (n, memo = {}) => {
if (n in memo) return memo[n];
if (n <= 1) return n;
memo[n] = fibFast(n-1, memo) + fibFast(n-2, memo);
return memo[n];
};
// fibFast(50) runs instantly; fibSlow(50) would take minutes
✅ Key Takeaways
- Big O notation describes worst-case growth — prefer O(log n) or O(n) over O(n²).
- Merge sort (O(n log n)) is optimal for comparison-based sorting of large datasets.
- Binary search requires sorted data but reduces a million-item search to ~20 comparisons.
- Recursion requires a base case; without one, it loops forever (stack overflow).
- Dynamic programming turns exponential brute-force into polynomial time by caching sub-results.
Object-Oriented Programming
Object-Oriented Programming (OOP) is a programming paradigm that organizes code around objects — bundles of data (fields) and behavior (methods). It enables large, complex systems to be built and maintained by modeling real-world entities and their relationships.
Classes & Objects
A class is a blueprint that defines the structure and behavior of objects. An object (or instance) is a concrete realization of that blueprint. Think of a class as the concept of a "Car" and an object as a specific car — your red Honda with 30,000 miles on it.
class BankAccount {
constructor(owner, balance = 0) {
this.owner = owner;
this.balance = balance;
this.#txLog = []; // private field (#)
}
deposit(amount) {
this.balance += amount;
this.#txLog.push(`+${amount}`);
}
withdraw(amount) {
if (amount > this.balance) throw new Error("Insufficient funds");
this.balance -= amount;
}
toString() {
return `${this.owner}: $${this.balance}`;
}
}
const acc = new BankAccount("Alice", 1000);
acc.deposit(500);
acc.withdraw(200);
console.log(acc.toString()); // "Alice: $1300"
The Four Pillars of OOP
class Shape {
area() { throw new Error("Subclass must implement area()"); }
toString() { return `Area: ${this.area().toFixed(2)}`; }
}
class Circle extends Shape {
constructor(r) { super(); this.r = r; }
area() { return Math.PI * this.r ** 2; }
}
class Rectangle extends Shape {
constructor(w, h) { super(); this.w=w; this.h=h; }
area() { return this.w * this.h; }
}
// Polymorphism: same method, different behavior
const shapes = [new Circle(5), new Rectangle(4,6)];
shapes.forEach(s => console.log(s.toString()));
// "Area: 78.54"
// "Area: 24.00"
✅ Key Takeaways
- Classes are blueprints; objects are instances with their own state.
- Encapsulation bundles data and methods, hiding implementation details.
- Inheritance lets child classes reuse and extend parent behavior.
- Polymorphism enables one interface to work across many types — the key to flexible design.
- Design around what an object does (its interface), not how it does it.
Computer Architecture
Understanding how a CPU actually executes your code — the physical journey from a high-level function call to billions of transistors switching state — transforms you from someone who writes programs into someone who understands them at the deepest level.
CPU Internals
The Central Processing Unit is made of three key components: the Arithmetic Logic Unit (ALU), which performs all math and comparisons; the Control Unit (CU), which orchestrates execution; and registers, the CPU's tiny, ultra-fast local storage (typically 16–32 registers, each 64 bits wide).
Fetch: load the next instruction from memory into the Instruction Register. Decode: the CU interprets the instruction's opcode. Execute: the ALU or memory system carries out the operation. This cycle runs billions of times per second.
Memory Hierarchy
Memory is arranged in a hierarchy: faster and smaller near the CPU, slower and larger further away. The CPU always tries to find data in the fastest available layer.
| Level | Size | Speed | Example |
|---|---|---|---|
| Registers | ~1 KB | < 1 ns | CPU internal (rax, rbx…) |
| L1 Cache | 32–128 KB | ~1 ns | Per-core, on-chip |
| L2 Cache | 256 KB – 4 MB | ~5 ns | Per-core or shared |
| L3 Cache | 8–64 MB | ~20 ns | Shared across cores |
| RAM (DRAM) | 8–128 GB | ~80 ns | Main memory |
| SSD Storage | 256 GB–4 TB | ~100 μs | NVMe drive |
| HDD Storage | 1–20 TB | ~5 ms | Spinning disk |
Cache misses are a major performance bottleneck. Writing code that accesses memory sequentially (good spatial locality) keeps data in cache and can be 100× faster than random access patterns.
Assembly Language Primer
Assembly is the human-readable form of machine code. Each assembly instruction maps one-to-one to a CPU instruction. Understanding it demystifies what compiled code actually does.
// C code: int add(int a, int b) { return a + b; }
// Compiles to x86-64 assembly (roughly):
add:
push rbp ; save base pointer
mov rbp, rsp ; set up stack frame
mov DWORD [rbp-4], edi ; store param 'a'
mov DWORD [rbp-8], esi ; store param 'b'
mov eax, [rbp-4] ; load a into register
add eax, [rbp-8] ; add b to a (result in eax)
pop rbp ; restore base pointer
ret ; return (eax holds return value)
Parallelism & Modern CPUs
Modern CPUs improve performance through pipelining (overlapping multiple instruction stages), superscalar execution (multiple ALUs running simultaneously), out-of-order execution (reordering instructions to avoid stalls), and branch prediction (guessing the next instruction path to avoid pipeline flushes).
✅ Key Takeaways
- The CPU's fetch-decode-execute cycle is the heartbeat of every computation.
- Memory hierarchy: registers → L1/L2/L3 cache → RAM → disk. Speed drops exponentially.
- Cache locality is critical — sequential memory access is dramatically faster than random.
- Assembly shows you the raw instructions your code compiles down to.
- Modern CPUs use pipelining, out-of-order execution, and branch prediction for speed.
Operating Systems
An Operating System is the master program that manages all hardware resources and provides services to applications. Without an OS, every program would need to directly control the keyboard, screen, disk, and network — a massive, duplicated effort. The OS abstracts hardware into clean, safe interfaces.
Processes & Threads
A process is a running program — it has its own memory space, file handles, and CPU state. A thread is a unit of execution within a process. Multiple threads share the same memory, enabling parallelism within a single application but introducing race conditions and the need for synchronization.
CPU Scheduling
On a single CPU, only one thread runs at a time. The scheduler rapidly switches between threads to create the illusion of concurrency. Common algorithms include Round Robin (each process gets equal time slices), Priority Scheduling (high-priority tasks run first), and Completely Fair Scheduler (Linux's approach using a red-black tree).
A deadlock occurs when two threads each hold a resource the other needs, and neither can proceed. Prevention requires careful lock ordering or using lock-free data structures.
Memory Management
The OS uses virtual memory to give each process the illusion of having the entire address space to itself. Physical memory is divided into fixed-size pages (typically 4KB). A page table maps each process's virtual addresses to physical ones. If a page isn't in RAM, a page fault triggers loading it from disk.
File Systems
A file system organizes data on storage devices into files and directories. Modern file systems include: NTFS (Windows), ext4 (Linux), APFS (macOS). They handle metadata (permissions, timestamps, sizes), journaling (crash recovery), and block allocation.
System Calls
Programs communicate with the OS through system calls — privileged operations like opening files, allocating memory, or creating processes. User code runs in user mode with restricted privileges; system calls briefly switch to kernel mode where the OS has full hardware access.
// File I/O
fd = open("/etc/hosts", O_RDONLY) // returns file descriptor
read(fd, buffer, 1024) // read up to 1024 bytes
write(fd, data, len)
close(fd)
// Process management
pid = fork() // duplicate current process
exec("/bin/ls", args) // replace process image
wait(pid) // wait for child to finish
// Memory
ptr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, ...)
munmap(ptr, 4096)
✅ Key Takeaways
- The OS manages CPU, memory, file systems, and I/O so programs don't have to.
- Processes have isolated memory; threads share it — enabling parallelism but requiring synchronization.
- The scheduler creates the illusion of concurrency on a finite number of CPU cores.
- Virtual memory gives each process its own address space via page tables.
- System calls are the gated interface between user programs and the OS kernel.
Databases
Nearly every application of any significance stores data persistently. Databases are the systems that make data storage, retrieval, and manipulation reliable, concurrent, and efficient. Understanding databases separates a capable developer from a proficient one.
The Relational Model
The relational model (E.F. Codd, 1970) organizes data into tables (relations) of rows and columns. Tables are linked by foreign keys — references to primary keys in other tables. This simple model has proven remarkably powerful for 50+ years.
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE posts (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
title VARCHAR(200),
body TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
SQL Queries
-- CREATE
INSERT INTO users (username, email)
VALUES ('alice', 'alice@example.com');
-- READ with JOIN
SELECT u.username, p.title, p.body
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE u.username = 'alice'
ORDER BY p.id DESC
LIMIT 10;
-- UPDATE
UPDATE users
SET email = 'newalice@example.com'
WHERE id = 1;
-- DELETE
DELETE FROM posts WHERE id = 42;
Normalization & Indexes
Normalization is the process of organizing tables to reduce data redundancy. The key normal forms: 1NF (atomic columns), 2NF (no partial dependencies), 3NF (no transitive dependencies). Well-normalized databases are easier to maintain and less prone to inconsistencies.
Indexes are auxiliary data structures (usually B-trees) that make queries faster. Without an index, a query must scan every row (O(n)). With one, it's O(log n). The trade-off: indexes speed up reads but slow down writes and consume storage.
ACID Properties
NoSQL Databases
NoSQL databases sacrifice some ACID guarantees for scalability and flexibility. Types include: Document (MongoDB — JSON-like), Key-Value (Redis — ultra-fast cache), Wide-Column (Cassandra — massive scale), Graph (Neo4j — relationships).
Use SQL (relational) when data has clear relationships and you need ACID guarantees. Use NoSQL for massive horizontal scale, flexible schemas, or specific access patterns (graph traversals, time series, caching).
✅ Key Takeaways
- The relational model (SQL) organizes data into tables connected by foreign keys.
- SQL's four core operations are SELECT, INSERT, UPDATE, DELETE (CRUD).
- Normalization reduces redundancy; indexes speed up reads at the cost of slower writes.
- ACID properties guarantee reliability in concurrent, failure-prone environments.
- NoSQL trades some guarantees for horizontal scalability and flexible data models.
Computer Networks
The internet — the largest network ever built — is a layered system of protocols. Understanding networking unlocks how web applications, APIs, security systems, and distributed services actually work beneath every HTTP request you've ever made.
The OSI & TCP/IP Models
Networking is divided into layers, each providing services to the layer above and abstracting the layer below. The OSI model has 7 conceptual layers; the TCP/IP model collapses these into 4 practical layers used in real implementations.
| TCP/IP Layer | OSI Equivalent | Protocol Examples | Responsibility |
|---|---|---|---|
| Application | 7-5 | HTTP, DNS, SMTP, FTP | User-facing protocols |
| Transport | 4 | TCP, UDP | End-to-end communication, ports |
| Internet | 3 | IP, ICMP, ARP | Routing across networks |
| Link | 2-1 | Ethernet, Wi-Fi | Physical transmission on local network |
TCP vs UDP
TCP (Transmission Control Protocol) provides reliable, ordered, error-checked delivery. It uses a three-way handshake (SYN → SYN-ACK → ACK) to establish a connection, and automatically retransmits lost packets. Used for web, email, file transfer — anything where every byte matters.
UDP (User Datagram Protocol) is connectionless and unreliable — packets may be lost, duplicated, or arrive out of order. But it is much faster. Used for video streaming, gaming, DNS, and VoIP where speed trumps perfect delivery.
IP Addresses & DNS
Every device on the internet has an IP address. IPv4 addresses are 32-bit numbers written as four octets (e.g., 192.168.1.1). IPv6 uses 128-bit addresses to accommodate billions more devices. Subnets divide address spaces into logical groups; NAT allows many devices to share one public IP.
DNS (Domain Name System) is the internet's phonebook: it translates human-readable names (google.com) into IP addresses that routers can use. DNS is hierarchical — root servers, TLD servers (.com, .org), authoritative servers for each domain.
HTTP/HTTPS
HTTP (HyperText Transfer Protocol) is the foundation of the web. It is a request-response protocol: the client sends a request, the server sends a response. HTTP is stateless — each request is independent.
── REQUEST ──────────────────────────────
GET /api/users/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGci...
Accept: application/json
── RESPONSE ─────────────────────────────
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
{
"id": 42,
"name": "Alice",
"email": "alice@example.com"
}
HTTPS wraps HTTP in TLS (Transport Layer Security), encrypting all traffic. A TLS handshake authenticates the server via a certificate and establishes a shared encryption key. The padlock icon in your browser means TLS is in use.
HTTP Status Codes
✅ Key Takeaways
- TCP/IP organizes networking into four layers: Application, Transport, Internet, and Link.
- TCP is reliable and ordered; UDP is fast but lossy — choose based on your use case.
- IP addresses uniquely identify devices; DNS maps domain names to IPs.
- HTTP is a stateless, text-based protocol; HTTPS encrypts it using TLS.
- HTTP status codes indicate success (2xx), redirect (3xx), client error (4xx), server error (5xx).
Web Development
Web development encompasses everything that makes websites and web applications work. It divides into frontend (what the user sees and interacts with) and backend (servers, databases, and logic running behind the scenes). Together they form the full-stack.
The Frontend Trinity
Every webpage is built from three languages that work in concert:
<!-- HTML defines the structure (content and semantics) -->
<article class="card">
<header>
<h2>Article Title</h2>
<time datetime="2025-01-01">Jan 1, 2025</time>
</header>
<p>Article content goes here.</p>
<a href="/read-more">Read more</a>
</article>
/* CSS defines how elements look */
.card {
background: #fff;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 12px rgba(0,0,0,.1);
transition: transform .2s;
}
.card:hover { transform: translateY(-4px); }
/* CSS Grid — modern two-column layout */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
// JavaScript brings pages to life
const btn = document.querySelector("#like-btn");
const count = document.querySelector("#like-count");
let likes = 0;
btn.addEventListener("click", () => {
likes++;
count.textContent = likes;
btn.classList.toggle("liked");
});
Frontend Frameworks
For complex, interactive applications, writing raw DOM manipulation becomes unwieldy. Modern frontend frameworks manage UI state and efficiently update only the parts of the DOM that change.
Backend Development & REST APIs
The backend handles business logic, database operations, authentication, and serving data to the frontend. A REST API (Representational State Transfer) is the most common style: it uses HTTP methods and URLs to perform CRUD operations on resources.
const express = require("express");
const app = express();
app.use(express.json());
let todos = [{ id:1, text:"Learn Node", done:false }];
app.get("/todos", (req, res) => {
res.json(todos); // GET all
});
app.post("/todos", (req, res) => {
const todo = { id: Date.now(), ...req.body };
todos.push(todo);
res.status(201).json(todo); // POST create
});
app.delete("/todos/:id", (req, res) => {
todos = todos.filter(t => t.id != req.params.id);
res.sendStatus(204); // DELETE
});
app.listen(3000);
✅ Key Takeaways
- HTML structures content, CSS styles it, JavaScript makes it interactive — they always work together.
- The DOM is the browser's in-memory model of the page; JavaScript manipulates it in real time.
- Frontend frameworks (React, Vue, Svelte) manage UI state efficiently through components.
- REST APIs use HTTP verbs (GET, POST, PUT, DELETE) to perform CRUD on resources.
- The full stack = frontend (browser) + backend (server) + database (persistence).
Software Engineering
Writing code that works once is easy. Writing code that is maintainable, testable, and scalable — that can be worked on by a team for years — is the discipline of software engineering. This module covers the practices that separate professionals from amateurs.
Version Control with Git
Git is the version control system used by virtually every software team in the world. It tracks every change to every file, lets multiple developers work in parallel on branches, and merges changes safely. GitHub, GitLab, and Bitbucket host Git repositories remotely.
# Initialize a new repo
git init
# Stage and commit changes
git add . # stage everything
git commit -m "feat: add login page"
# Branching workflow
git checkout -b feature/auth # create + switch branch
git merge feature/auth # merge into current branch
git rebase main # replay commits on top of main
# Remote operations
git clone https://github.com/user/repo.git
git push origin feature/auth
git pull origin main
# Inspect
git log --oneline --graph --all # visual branch history
git diff HEAD~1 # changes since last commit
Testing
Automated testing is the practice of writing code that verifies your code works. Without tests, refactoring is terrifying — any change might break something undetected. The testing pyramid describes three levels:
// sum.js
function sum(a, b) { return a + b; }
module.exports = sum;
// sum.test.js
const sum = require("./sum");
describe("sum function", () => {
test("adds two positive numbers", () => {
expect(sum(1, 2)).toBe(3);
});
test("handles negative numbers", () => {
expect(sum(-1, 5)).toBe(4);
});
test("returns 0 for 0 + 0", () => {
expect(sum(0, 0)).toBe(0);
});
});
Design Patterns
Design patterns are reusable solutions to commonly occurring design problems. Documented by the "Gang of Four" (GoF) in 1994, they give developers a shared vocabulary. Key patterns:
- Singleton: Ensures a class has only one instance (e.g., a configuration object or database connection pool).
- Observer: Objects subscribe to events; the publisher notifies all subscribers when something changes. Foundation of event systems and UI frameworks.
- Factory: Encapsulates object creation logic so callers don't need to know which specific class to instantiate.
- Strategy: Defines a family of algorithms and makes them interchangeable at runtime.
- MVC (Model-View-Controller): Separates data (Model), display (View), and logic (Controller). Used by virtually every web framework.
Agile & Software Development Lifecycle
The Software Development Lifecycle (SDLC) encompasses requirements → design → implementation → testing → deployment → maintenance. Agile is a family of iterative methodologies that favors short development cycles ("sprints"), continuous feedback, and adapting to change over rigid upfront planning. Scrum is the most popular Agile framework.
✅ Key Takeaways
- Git tracks every change; branching enables parallel development without conflict.
- Automated tests are safety nets — write them especially before refactoring.
- The testing pyramid: many unit tests, fewer integration tests, minimal E2E tests.
- Design patterns are proven solutions; knowing them avoids reinventing the wheel.
- Agile iterates quickly — ship small, learn fast, adapt continuously.
Theory of Computation
Theory of Computation asks: What are the fundamental limits of what computers can do? This is pure mathematics applied to machines. Grasping these ideas separates those who use computer science from those who truly understand it.
Formal Languages & Automata
A formal language is a set of strings over some alphabet, defined by rules. Automata are abstract machines that recognize languages. The hierarchy of languages and their corresponding machines is the Chomsky hierarchy:
| Language Type | Machine | Example |
|---|---|---|
| Regular | Finite Automaton (DFA/NFA) | Simple patterns, lexer tokens |
| Context-Free | Pushdown Automaton | Programming language grammars |
| Context-Sensitive | Linear Bounded Automaton | Natural language (roughly) |
| Recursively Enumerable | Turing Machine | Any computable problem |
Regular Expressions
Regular expressions (regex) define regular languages — the simplest class. They are implemented by compiling to a DFA. Every regex engine you've used is, at its core, an automaton.
// Regex syntax (used in most languages)
/^\d+$/ // one or more digits, entire string
/^[a-zA-Z]{3,}$/ // 3+ letters only
/\b\w+@\w+\.\w+\b/ // crude email pattern
/https?:\/\/.+/ // URL starting with http or https
// In JavaScript:
const emailRe = /^[\w.-]+@[\w.-]+\.[a-z]{2,}$/i;
emailRe.test("user@example.com"); // true
emailRe.test("notanemail"); // false
// Regex CANNOT match nested structures like ((())) — need CFG for that
The Turing Machine
A Turing Machine (Alan Turing, 1936) is an abstract machine with an infinite tape, a read/write head, a finite set of states, and a transition table. It can compute anything a modern computer can compute — they are computationally equivalent. The Turing Machine defines the outer boundary of what is computable.
Any function that is "effectively computable" by any physical process can be computed by a Turing Machine. This is not a theorem — it cannot be proved — but it is universally accepted. It means the limits of the Turing Machine are the limits of all possible computers.
Decidability & the Halting Problem
A problem is decidable if there exists a Turing Machine that always halts and gives the correct yes/no answer for any input. The Halting Problem (does a given program halt on a given input?) is undecidable — Turing proved no algorithm can solve it in general. This is a fundamental limit of computation, not of current technology.
Complexity Theory: P vs NP
P (polynomial time) is the class of problems solvable in O(n^k) time — "efficiently solvable." NP (nondeterministic polynomial) is the class of problems where a given solution can be verified in polynomial time — though finding the solution may be hard. The P vs NP question asks: is every problem whose solution is easily verifiable also easily solvable? It is the most famous unsolved problem in mathematics and CS.
Most cryptographic security (RSA, elliptic curves) relies on the assumption that certain NP problems (factoring large integers) are not in P. If P = NP, modern encryption would collapse.
✅ Key Takeaways
- The Chomsky hierarchy classifies languages by complexity: regular → context-free → Turing-recognizable.
- Regular expressions match regular languages and compile down to finite automata.
- Turing Machines define the limits of computation — any physical computer is equivalent.
- The Halting Problem is provably undecidable — no algorithm can solve it for all inputs.
- P vs NP is unsolved; most cryptography depends on NP-hard problems being intractable.
Artificial Intelligence & Machine Learning
Artificial Intelligence has moved from science fiction to the engine of the modern economy. Machine Learning — the subset of AI where systems learn from data rather than explicit rules — now powers recommendation systems, language models, autonomous vehicles, medical diagnosis, and much more.
Types of Machine Learning
Linear Regression: The Foundation
The simplest ML model is linear regression — fitting a line through data points. Despite its simplicity, it encapsulates the fundamental ML loop: define a model, a loss function, and an optimization algorithm.
import numpy as np
# Dataset: hours studied → exam score
X = np.array([1, 2, 3, 4, 5])
y = np.array([55, 60, 70, 75, 85])
# Model: y_hat = w * x + b
w, b = 0.0, 0.0
lr = 0.01 # learning rate
n = len(X)
for epoch in range(1000):
y_hat = w * X + b # forward pass
error = y_hat - y # residuals
loss = (error**2).mean() # mean squared error
# gradients (calculus)
dw = (2/n) * (error * X).sum()
db = (2/n) * error.sum()
# gradient descent update
w -= lr * dw
b -= lr * db
print(f"w={w:.2f}, b={b:.2f}") # w≈6.0, b≈48.0
Neural Networks
A neural network is a chain of layers, each containing neurons. Each neuron computes a weighted sum of its inputs and applies a nonlinear activation function. By stacking many layers, networks can approximate arbitrarily complex functions — this is the universal approximation theorem.
Training works via backpropagation: compute the loss on the output, calculate gradients using the chain rule (calculus), and update each weight in the direction that reduces the loss. Repeat for thousands of iterations until the network performs well.
import torch
import torch.nn as nn
# A simple feed-forward network
model = nn.Sequential(
nn.Linear(784, 256), # input layer (28×28 pixel image)
nn.ReLU(), # activation function
nn.Linear(256, 128), # hidden layer
nn.ReLU(),
nn.Linear(128, 10), # output layer (10 digit classes)
)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
for x, y in dataloader:
pred = model(x) # forward pass
loss = loss_fn(pred, y) # compute error
loss.backward() # backpropagate gradients
optimizer.step() # update weights
optimizer.zero_grad() # clear gradients for next batch
Deep Learning & Modern AI
Deep learning refers to networks with many layers (dozens to thousands). Breakthroughs include CNNs (Convolutional Neural Networks, for images), RNNs (Recurrent Networks, for sequences), and Transformers (2017, "Attention Is All You Need") — the architecture behind every large language model (GPT-4, Claude, Gemini).
Modern AI systems are trained on enormous datasets and compute, then fine-tuned and aligned to specific tasks using techniques like RLHF (Reinforcement Learning from Human Feedback).
✅ Key Takeaways
- ML learns from data; the three main paradigms are supervised, unsupervised, and reinforcement learning.
- Every ML model involves: architecture (structure), loss function (error measure), optimizer (training algorithm).
- Neural networks stack layers of weighted transformations with nonlinear activations.
- Backpropagation uses calculus (chain rule) to compute gradients and update weights.
- Transformers and attention mechanisms power all modern large language models.
Cybersecurity
Every system connected to a network is a potential target. Security is not a feature you add at the end — it is a discipline woven through every layer of software and infrastructure. This module introduces the principles, attacks, and defenses every developer must understand.
The CIA Triad
Cryptography
Symmetric encryption uses the same key to encrypt and decrypt (AES-256). It's fast but requires securely sharing the key. Asymmetric encryption uses a public/private key pair: anything encrypted with the public key can only be decrypted with the private key (RSA, ECC). HTTPS uses asymmetric crypto to exchange a symmetric key, then switches to symmetric for speed.
Hashing transforms input into a fixed-length, irreversible digest. SHA-256 produces a 256-bit hash. Even a single changed character produces a completely different hash. Used for password storage (with salting) and data integrity.
// Password hashing (Node.js with bcrypt)
const bcrypt = require("bcrypt");
// Storing a password — NEVER store plaintext
const hash = await bcrypt.hash("user_password", 12);
// "$2b$12$X7hVkRq3…" (salt is embedded in hash)
// Verifying a password on login
const ok = await bcrypt.compare("user_password", hash);
// JWT (JSON Web Token) — stateless auth
const jwt = require("jsonwebtoken");
const token = jwt.sign({ userId: 42 }, process.env.SECRET, { expiresIn: "24h" });
const payload = jwt.verify(token, process.env.SECRET);
Common Attacks
Knowing how attacks work is the first step to preventing them. The OWASP Top 10 lists the most critical web security risks:
// ❌ VULNERABLE — user input directly in query string
const query = `SELECT * FROM users WHERE email='${req.body.email}'`;
// Attacker inputs: ' OR '1'='1 → returns ALL users!
// ✅ SAFE — parameterized queries (the only correct approach)
const user = await db.query(
"SELECT * FROM users WHERE email = $1",
[req.body.email] // driver escapes it automatically
);
// XSS (Cross-Site Scripting) — sanitize all user output
// ❌ Vulnerable: element.innerHTML = userInput
// ✅ Safe: element.textContent = userInput
| Attack | Description | Defense |
|---|---|---|
| SQL Injection | Malicious SQL in user input | Parameterized queries / ORMs |
| XSS | Injecting scripts into web pages | Output encoding, CSP headers |
| CSRF | Forged requests from another site | CSRF tokens, SameSite cookies |
| Phishing | Tricking users into revealing credentials | MFA, security training |
| DDoS | Flooding a server with traffic | Rate limiting, CDN, WAF |
| Broken Auth | Weak passwords, session theft | MFA, secure sessions, bcrypt |
Security Principles & Best Practices
- Principle of Least Privilege: Every component should have only the permissions it strictly needs.
- Defense in Depth: Layer multiple controls so a single failure doesn't compromise the system.
- Secure by Default: Default configurations should be the most secure, not the most convenient.
- Never Trust User Input: Validate and sanitize everything arriving from outside your system.
- Keep Secrets Secret: API keys, passwords, and tokens live in environment variables or secret managers — never in source code.
- Update Dependencies: Most breaches exploit known vulnerabilities in outdated libraries.
Authentication verifies who you are (login). Authorization verifies what you're allowed to do (permissions). Both must be enforced on the server — never trust the client to enforce either.
🎓 Curriculum Complete!
- The CIA triad (Confidentiality, Integrity, Availability) is the foundation of all security thinking.
- Asymmetric cryptography enables HTTPS; symmetric crypto provides fast bulk encryption.
- SQL injection is prevented exclusively by parameterized queries — string concatenation is never safe.
- XSS is prevented by encoding output; CSRF by tokens and SameSite cookie policy.
- Least privilege, defense in depth, and secure-by-default are the core architectural principles.