Free
free
Foundations
Algorithms and Efficiency
You already know how to write code that works. But here is a question most beginners never ask: *does it matter how you solve it?* If two programs both give the correct answer, are they equally good? The answer, it turns out, is a decisive no -- and understanding why is the foundation of everything in this course. **Algorithms and Efficiency** is the opening lesson of the DSA Foundations track. Rather than jumping straight into notation or formulas, it starts with a simple, concrete idea: different solutions to the same problem can do very different amounts of work, and that gap in effort becomes enormous once your input is large. You will see two approaches to finding the maximum number in a list, watch the operation counts diverge as the list grows, and come away with a clear intuition for what "efficiency" means in practice. No O() notation yet -- just the honest, visual realization that some code does way more work than it needs to. This lesson bridges everything you already know about writing code to the new skill of *reasoning about* code. You can write loops, call functions, and store values in variables -- that is all you need. By the end, you will have a concrete sense of what engineers mean when they say one algorithm is "better" than another, and you will understand exactly why input size is the key variable in that judgment. Next up is **Counting Operations** (lesson 2), where you will formalize the intuition you build here by learning to count steps precisely as a function of the input size `n`. From there, the track moves into Asymptotic Analysis and Big-O Notation -- the symbolic language the entire industry uses to express these ideas.
Not Started
0%
Asymptotic Analysis Fundamentals
How can you tell which of two algorithms will scale to a million inputs without ever running them on real hardware? Wall-clock timing fails this test because it depends on your CPU, your language, and even what other apps are open, so we need a more durable measure of efficiency. The word *asymptotic* looks intimidating, but the idea behind it is simple: it describes how a function behaves as its input grows very large. **Asymptotic Analysis Fundamentals** uses that idea to build a hardware-independent measure of efficiency — by counting how the number of operations an algorithm performs grows as the input size `n` increases, you get a yardstick that works regardless of your CPU, language, or environment. You will see why basic operation counting beats stopwatch timing, how growth rates rank from constant to exponential, why constant factors and lower-order terms drop out of the analysis, and what it really means for `n` to represent the size of an input. In **Counting Operations**, you built the habit of tracing through code and tallying how many steps it performs as input size `n` grows. Every operation count you produced there — a loop running `n` times, nested loops running `n * n` times — becomes exactly the formula this lesson formalizes. Asymptotic analysis is the language that turns those raw counts into a precise, hardware-independent way to compare algorithms. Once you are comfortable thinking in growth rates, you will be ready for **Big-O Notation (Upper Bound)**, the formal symbolic language (think `O(n)`, `O(n^2)`, `O(log n)`) used everywhere in textbooks, interviews, and engineering documentation to express the ideas you build here.
Not Started
0%
Big-O Notation (Upper Bound)
How do you compare two solutions to the same problem without ever running them on real hardware? Big-O is the symbolic shorthand that the entire industry uses to do exactly that, and it is the single most referenced notation in textbooks, technical interviews, and engineering documentation. **Big-O Notation (Upper Bound)** turns the growth-rate ideas you have been thinking about into precise notation. You will see what `O(f(n))` formally means as an upper bound, walk through the seven complexity classes you will meet again and again (`O(1)`, `O(log n)`, `O(n)`, `O(n log n)`, `O(n^2)`, `O(2^n)`, `O(n!)`), and learn the rules for reading Big-O straight from code: single loops, nested loops, sequential blocks, conditional branches, and inputs with multiple variables like `O(n + m)` and `O(n * m)`. You will also meet best, worst, and average case analysis so you know which scenario a Big-O statement is describing. In **Asymptotic Analysis Fundamentals**, you saw why we count operations as a function of input size `n`, why constants and lower-order terms drop away, and how growth rates rank from constant to exponential. Big-O is the formal language for everything you reasoned about there: each growth-rate intuition becomes a concrete `O(...)` expression. Once you can classify code by sight, you will be ready for **Time Complexity Analysis Techniques**, where you will apply these rules to longer, more realistic snippets and develop a systematic analysis workflow.
Not Started
0%
Big-Omega Notation (Lower Bound)
If `O(n)` is the worst-case ceiling on an algorithm's running time, what tells you the floor, the absolute minimum work the algorithm must always do? That is the question **Big-Omega Notation** answers, and it is the second pillar of asymptotic analysis that turns half a description of performance into the full picture. This lesson introduces `Omega(g(n))` as a formal lower bound, with the mirror-image definition of Big-O: there exist positive constants `c` and `n0` such that `f(n) >= c * g(n)` for all `n >= n0`. You will practice proving Omega claims, analyze the best-case behavior of familiar algorithms (linear search, bubble sort, insertion sort), and meet a famous problem lower bound: any comparison-based sort must do at least `Omega(n log n)` comparisons in the worst case. You will also learn to spot when an algorithm is asymptotically optimal because its upper and lower bounds coincide. This lesson builds directly on **Big-O Notation (Upper Bound)**, where you learned to express upper bounds on growth using constants `c` and `n0`. Big-Omega flips the inequality from `<=` to `>=` and reuses the same machinery to describe floors instead of ceilings. Once you are comfortable bracketing algorithms with both bounds, you will be ready for **Big-Theta Notation (Tight Bound)**, where the upper and lower bounds meet to give the most precise complexity statement possible.
Not Started
0%
Big-Theta Notation (Tight Bound)
When you can say *both* that an algorithm runs in `O(n log n)` and that it must do at least `Omega(n log n)` work, the ceiling and floor have collapsed onto the same growth rate. That coincidence is the most informative complexity statement you can make, and it is what **Big-Theta Notation** captures in a single symbol. This lesson defines `Theta(g(n))` as the intersection of Big-O and Big-Omega: there exist positive constants `c1`, `c2`, and `n0` such that `c1 * g(n) <= f(n) <= c2 * g(n)` for all `n >= n0`, the so-called sandwich property. You will practice classifying functions and algorithms by their tight bound, learn when Big-Theta is appropriate (best and worst case have the same growth rate) versus when only Big-O is honest (the cases differ), and see why merge sort is `Theta(n log n)` while linear search is *not* Theta of any single function. This lesson stitches together two ideas you have already met. From **Big-O Notation (Upper Bound)** you have the ceiling `f(n) <= c * g(n)`, and from **Big-Omega Notation (Lower Bound)** you have the floor `f(n) >= c * g(n)`. Big-Theta simply requires both bounds to hold for the same `g(n)`, so an algorithm only earns a Theta label when its upper and lower bounds genuinely match. With all three asymptotic notations in hand, you will be ready to switch focus to **Memory Models**, where the same growth-rate thinking gets applied to space rather than time.
Not Started
0%
Combinatorics Basics
How many ways can you arrange `n` items? How many subsets does a set of size `n` have? Questions like these are not idle puzzles, they are exactly the questions you have to answer to know whether a brute-force solution will run in milliseconds or in millennia. **Combinatorics Basics** gives you the counting tools that turn vague statements like "try every possibility" into precise complexity claims. This lesson covers the four counting ideas you will rely on throughout DSA. The **rule of sum** and **rule of product** let you decompose multi-step processes and count outcomes; **permutations** count ordered arrangements (`n!` for arranging all of them, `nPr` for choosing `r` in order); **combinations** count unordered selections (`nCr`, also written as binomial coefficients); and **Pascal's triangle** plus a first look at **basic probability** tie everything together. You will see why a set of size `n` has `2^n` subsets, why generating all permutations of an array is `O(n!)`, and how counting principles directly produce the time complexity of backtracking algorithms. This lesson builds on **Discrete Mathematics Basics**, where you formalized sets, set operations, and logic. Combinatorics is what happens when you start asking "how many?" about those sets, ordered or unordered, with or without repetition. With the counting toolkit in place you will continue the math track in **Number Theory Fundamentals**, picking up primes, divisibility, and `gcd`, the other half of the math you need for hashing, cryptography-style problems, and modular arithmetic.
Not Started
0%
Counting Operations
In **Algorithms and Efficiency** you saw the core question: when two programs solve the same problem, how do you know which one scales better? The answer lies in *counting*. Before any formal notation, before any mathematical symbols, there is a hands-on skill that every strong programmer develops: looking at a piece of code and tallying the work it performs as the input grows. **Counting Operations** teaches exactly that skill. You will learn what counts as a single operation, how to trace a loop and tally its iterations, how to build an operations table that reveals the relationship between input size and work, and what happens when loops are nested. By the end of this lesson you will be able to look at a code snippet and say: "for input size n = 10 this runs about 10 steps; for n = 100, about 100 steps; for n = 1000, about 1000 steps" and mean it precisely. You will also compare two solutions to the same problem side by side -- counting operations for each as n grows -- and experience the lightbulb moment: when inputs are small both solutions feel fine, but as n climbs the difference becomes impossible to ignore. That concrete, tangible understanding of scaling behaviour is the foundation everything else rests on. In the next lesson, **Asymptotic Analysis Fundamentals**, you will take exactly these operation counts and formalize them with the mathematical framework that makes comparison rigorous. Everything you practice counting here becomes the raw material for the O() notation and growth-rate rankings you will learn there.
Not Started
0%
Debugging by Tracing
Most beginners debug by staring at their code and hoping the bug reveals itself. Engineers who ship working software do something different: they trace execution one line at a time, watch each variable, and pinpoint the exact moment the program's state stops matching their mental model. That motion is a learnable, repeatable skill. **Debugging by Tracing** turns that skill into a structured workflow. You will define what program state actually is (the values of every variable plus the current line of execution), practice walking through a function step by step recording each change, and learn to use loop invariants (conditions that must hold before and after every iteration) to verify correctness without running the code. You will work through a systematic process of reproduce, trace, locate, fix, and verify, and you will see the most common beginner pitfalls including off-by-one errors, uninitialized variables, and misordered swaps. In **How to Read Code (JS & Python)**, you practiced tracing variables through `for` and `while` loops and stepping through function calls on the call stack. This lesson takes that same tracing motion and aims it at a specific goal: finding the line where a bug first introduces wrong state, instead of just understanding what correct code is doing. With Tier 1 essentials behind you, you will be ready to dive into the data structures track starting with **Arrays & Strings**, where every algorithm you write will benefit from being traceable and provably correct.
Not Started
0%
Discrete Mathematics Basics
Almost every conditional you have ever written is a tiny piece of propositional logic, and almost every collection you have ever stored is, mathematically, a set. **Discrete Mathematics Basics** makes that connection explicit, giving you the formal vocabulary that data structures, graph algorithms, database queries, and correctness arguments all sit on top of. This lesson walks you through three pillars of discrete math used throughout DSA. First, **sets** and the operations on them: union, intersection, difference, complement, subset, and membership, the same ideas baked into hash sets and array problems. Second, **propositional logic**: the operators `AND`, `OR`, `NOT`, and implication, plus truth tables for evaluating compound expressions; this is the math behind every Boolean condition in your code. Third, **binary relations** and their properties (reflexive, symmetric, transitive), which generalize the way edges connect nodes in graphs and rows connect in databases. Along the way you will get a first taste of formal proof: how to argue that two sets are equal or that a logical equivalence always holds. This lesson assumes only basic programming familiarity, so no specific DSA prerequisite is required. If you have written `if (x && !y)` or used a hash set, you already have informal intuition for everything we will formalize here. From here you will move into **Combinatorics Basics**, where you will use these set and counting foundations to count permutations, combinations, and arrangements (the math behind backtracking and many complexity proofs).
Not Started
0%
Logarithms & Exponentiation
How can a search through a million sorted items finish in roughly twenty comparisons? The answer is `log_2(n)`, and once you internalize what that expression really means, an entire family of fast algorithms (binary search, balanced trees, divide-and-conquer) suddenly stops feeling like magic. **Logarithms & Exponentiation** builds your intuition for the math behind `O(log n)`. You will start from a concrete framing (a logarithm answers the question "how many times can I halve `n` before reaching 1?") and work through the powers-of-2 sequence (1, 2, 4, 8, 16, ...) that appears constantly in computer science. You will pick up the three logarithm properties you actually need (`log(a * b) = log(a) + log(b)`, `log(a^n) = n * log(a)`, and `log(1) = 0`), see why the base does not matter inside Big-O, meet fast exponentiation as a preview of `O(log n)` in action, and trace why every halving loop produces logarithmic complexity. In **Big-O Notation (Upper Bound)**, you learned that `O(log n)` sits between `O(1)` and `O(n)` in the hierarchy and is associated with algorithms that halve their work each step. This lesson supplies the mathematical reason that pattern produces a logarithm, so the complexity class stops being something you memorize and starts being something you can derive. Once logarithms feel comfortable, you will be ready for **Mathematical Sequences**, where you will study arithmetic sums, geometric series, and Fibonacci patterns that explain the running times of everything from nested loops to recursive algorithms.
Not Started
0%
Mathematical Sequences
Why is a triangular nested loop that does `1 + 2 + 3 + ... + n` units of work classified as `O(n^2)` rather than `O(n)`? Because the closed form of that sum is `n(n+1)/2`, which grows quadratically. The same kind of summation argument explains why merge sort is `O(n log n)`, why a doubling array gives amortized `O(1)` insertions, and why naive recursive Fibonacci is exponentially slow. **Mathematical Sequences** equips you with the essential sequences and summation formulas behind algorithm analysis. You will work with arithmetic sequences (where each term adds a constant) and the formula `1 + 2 + ... + n = n(n+1)/2`, geometric sequences (where each term multiplies by a constant ratio) and their geometric-series sum, and the Fibonacci sequence defined by `F(n) = F(n-1) + F(n-2)`. You will then connect these formulas back to code: triangular nested loops, halving-and-doubling patterns, and the canonical recursive Fibonacci that motivates dynamic programming. In **Big-O Notation (Upper Bound)**, you learned to spot dominant terms and to drop constants and lower-order terms. This lesson supplies the closed-form summations that turn a step count like `1 + 2 + ... + n` into a precise polynomial you can simplify with confidence. With these summation tools in hand, you will be ready for **Debugging by Tracing**, where you will sharpen the step-by-step execution skills you need to verify that the algorithms you analyze actually behave the way your formulas predict.
Not Started
0%
Memory Models
Why does a deeply recursive function crash with a stack overflow while an equivalent loop happily processes a million elements? Why does passing an array into a function sometimes mutate the caller's data and sometimes not? The answers all live one level below your code, in the runtime **memory model** that decides where every variable, object, and function call physically lives. This lesson opens up that model. You will see the difference between the **stack** (small, fast, organized into per-call frames) and the **heap** (larger, manually or garbage-collected, where objects and arrays actually live), watch how every function call pushes a stack frame and every `return` pops one, and trace how recursion stacks frames on top of frames until the base case unwinds them. You will also meet references and pointers conceptually, learn what triggers a stack overflow, and get a working mental model of garbage collection and memory leaks. This lesson builds on **Space Complexity Fundamentals**, where you learned to count auxiliary versus total space and saw why some algorithms claim `O(1)` space while others need `O(n)`. Memory Models gives you the underlying picture: those `O(...)` numbers describe stack frames, heap allocations, and reference graphs that you can now visualize concretely. Next, you will pivot to the mathematical side of the foundations track with **Discrete Mathematics Basics**, picking up the sets, relations, and logic vocabulary that algorithm analysis depends on.
Not Started
0%
Number Theory Fundamentals
Why are hash table sizes almost always prime? Why does the Euclidean algorithm for `gcd` run in `O(log(min(a, b)))` time despite using only subtraction or remainders? **Number Theory Fundamentals** is the branch of math that answers these questions and quietly powers a surprising amount of practical software, from hashing and cryptography to scheduling and competitive programming shortcuts. This lesson introduces the core building blocks. You will study **prime numbers** and how to test primality efficiently, the **divisibility** relation and quick rules for spotting divisors, **GCD** (greatest common divisor) and **LCM** (least common multiple) with the identity `lcm(a, b) = a * b / gcd(a, b)`, and the **Euclidean algorithm** for computing `gcd` in logarithmic time. You will also do **prime factorization** by trial division (`O(sqrt(n))`) and see how factorizations make divisor counting, simplification, and periodicity arguments straightforward. This lesson builds on **Discrete Mathematics Basics**, where you learned how to reason precisely about sets, relations, and logical claims. Number theory uses that same proof-style reasoning, but applied to the integers and their multiplicative structure. With primes, `gcd`, and factorization in your toolkit, you will be ready to take the premium step into **Modular Arithmetic**, where you will reuse all of these ideas to work with `mod`, modular inverses, and Fermat's Little Theorem.
Not Started
0%
Space Complexity Fundamentals
An algorithm that runs in `O(n)` time but allocates a fresh array of size `n^2` will not fit in memory long before it would have finished computing. Memory is finite, and many algorithms quietly trade it for speed; the only way to make that trade-off visible is to analyze space the same way you analyze time. **Space Complexity Fundamentals** extends the Big-O lens from operation counting to memory usage. You will see why we draw a line between auxiliary space (the extra storage an algorithm allocates) and total space (auxiliary plus the input itself), and you will classify common algorithms as `O(1)` constant, `O(n)` linear, or `O(n^2)` quadratic in space. You will meet the in-place vs out-of-place distinction that interviewers love to probe, and you will study a first space-time trade-off where caching results turns repeated work into stored data. In **Big-O Notation (Upper Bound)**, you learned to read complexity classes from loop structure and to recognize that `O(c * n)` collapses to `O(n)`. The same vocabulary and the same simplification rules apply here, but the resource being counted is bytes of allocated memory rather than units of work. Once you can reason about both axes at once, you will be ready for **Logarithms & Exponentiation**, where you will build the mathematical intuition behind `O(log n)`, the complexity class that powers binary search, balanced trees, and most efficient divide-and-conquer algorithms.
Not Started
0%
Time Complexity Analysis Techniques
Recognizing `O(n)` or `O(n^2)` on a slide is one thing; staring at unfamiliar code in an interview and confidently announcing its complexity is another. The gap between those two skills is filled by analysis technique, and that technique is what this lesson hands you. **Time Complexity Analysis Techniques** turns Big-O classification into a repeatable workflow you can apply to any snippet. You will analyze single loops with non-standard step sizes, nested loops where the inner bounds depend on the outer variable, sequential blocks that you sum and simplify, `if`/`else` branches where you take the worst case, and the telltale halving-or-multiplying patterns that produce `O(log n)`. Along the way you will see why a triangular loop summing `0 + 1 + 2 + ... + (n-1)` lands at `O(n^2)`, and why constant-bounded loops collapse to `O(1)` no matter how many times they run. In **Big-O Notation (Upper Bound)**, you learned what `O(f(n))` means and how to spot the dominant term in a polynomial. This lesson sharpens that recognition into a procedure: count iterations precisely, multiply for nesting, add for sequencing, and drop everything but the dominant term. Once you can dissect time complexity from real code, you will be ready for **Space Complexity Fundamentals**, where you apply the same Big-O lens to memory usage instead of operation counts.
Not Started
0%
Data Structures
Arrays & Strings
Reading `arr[1000000]` takes the same amount of time as reading `arr[0]`, because an array's contiguous memory layout lets the runtime compute the address of any element with a single multiplication. That single property is what makes **Arrays & Strings** the workhorse data structure of nearly every program you will ever write. This lesson walks through array declaration and indexing in JavaScript and Python, the cost of inserts and deletes that force shifting, slicing and subarray patterns, and the basic string operations every interview problem assumes you know. You will also see why strings behave as immutable character arrays in both languages and what that means for performance when you build strings inside a loop. In **How to Read Code (JS & Python)**, you practiced tracing programs line by line; that habit is exactly how you will reason about loop indices and slice boundaries here. **Big-O Notation (Upper Bound)** gave you the language to express scaling behavior, and arrays are where you will first see `O(1)` access sit next to `O(n)` insertion in the same data structure. Once array fundamentals are second nature, **Matrix/Grid Fundamentals** extends the same indexing intuition into two dimensions, where row and column traversal patterns power BFS, DFS, and dynamic programming on grids.
Not Started
0%
Trees: Binary Tree Fundamentals
Every web page you load is a tree (the DOM), every directory you `cd` into is a tree, every JSON document with nested objects is a tree, and every arithmetic expression a compiler parses is a tree. **Binary Tree Fundamentals** turns that ubiquitous shape into a precise data structure where each node has at most two children, named `left` and `right`. This lesson covers the vocabulary you need for the rest of the curriculum (root, leaf, parent, child, sibling, depth, height), the four common shape categories (full, complete, perfect, balanced) and the degenerate case that collapses to a chain, all four canonical traversals (preorder, inorder, postorder, and level-order BFS), and both recursive and iterative implementations of each. You will also analyze why traversals are `O(n)` time but only `O(h)` space when written recursively. In **Queue & Deque**, you saw that a FIFO queue processes items in arrival order; level-order traversal is exactly that pattern applied to tree nodes. In **Hash Map (Dictionary) Basics**, the bucket-by-key idea reappears here when you store parent-pointer mappings or memoize subtree results during a traversal. With binary trees in your toolkit, **Binary Search Tree (BST)** layers an ordering invariant on top to turn the same shape into a logarithmic-time search structure.
Not Started
0%
Graphs: Representation Basics
A social network is a graph, a road map is a graph, an `import` graph in your codebase is a graph, and so is the dependency tree of a package manager. The reason graphs feel everywhere is that they are the most general relationship structure: just vertices and edges, with no required root, no acyclic constraint, and no fixed branching factor. **Graphs: Representation Basics** is where you stop drawing them on whiteboards and start storing them in code. This lesson defines the core vocabulary (vertex, edge, degree, path, cycle, directed and undirected, connected and disconnected) and walks through the three canonical representations: an adjacency list backed by a hash map of vertex to neighbors, an adjacency matrix for dense graphs and `O(1)` edge-existence queries, and an edge list for algorithms that just iterate over edges. You will analyze the space and time trade-offs of each so you can pick the right one before writing any traversal logic. In **Hash Map (Dictionary) Basics**, you saw that a hash map maps keys to values in expected `O(1)`; an adjacency list is exactly that, mapping each vertex to its list of neighbors. **Queue & Deque** gave you the FIFO machinery that BFS will run on top of these representations once traversal lessons begin. With a graph stored in memory, the algorithms track is where it comes alive: BFS, DFS, shortest paths, connected components, and the rest of the graph algorithm canon.
Not Started
0%
Hash Map (Dictionary) Basics
Two Sum is the canonical example: scan the array once, and for every number `x` ask the data structure whether you have already seen `target - x`. With a list that is `O(n^2)`. With a **Hash Map**, the lookup becomes a single function call and the whole algorithm collapses to `O(n)`. This lesson covers the key-value abstraction, the intuition behind a hash function (a key gets converted to a bucket index by an integer-valued function), what a collision is, and how `get`, `set`, `delete`, and `has` behave in JavaScript's `Map` and Python's `dict`. You will also meet two recurring patterns: counting frequencies and bucketing/grouping by a derived key, both of which appear in dozens of interview problems and real applications such as analytics, caching, and indexing. In **Arrays & Strings**, you saw that searching an unsorted array is `O(n)` because every position is equally plausible. A hash map turns that linear scan into an arithmetic computation that points directly at the right slot, which is the entire reason hash-based lookups dominate practical software. With key-based lookup in hand, **Set Basics** specializes the same machinery to a single question: have I seen this value before?
Not Started
0%
Linked Lists (Singly)
Inserting at the front of an array of one million items forces every existing element to slide one slot to the right. A **Linked List** sidesteps that copy entirely: a head insertion is two pointer assignments and nothing else, no matter how many nodes follow. In this lesson, each node holds a value and a `next` reference, and you chain those nodes into a singly linked list with explicit head and optional tail pointers. You will implement traversal, head and tail and middle insertion, deletion by value and by position, and linear search, and you will analyze each operation precisely so the trade-off against arrays becomes concrete: `O(1)` insertion at the head, `O(n)` access by index, and per-node memory overhead for the pointer. In **Arrays & Strings**, the `O(n)` cost of inserting near the front came from shifting; the linked list was invented to remove that shift. **Memory Models** introduced the difference between a contiguous block on the stack or heap and scattered allocations connected by references, and that mental model is exactly how a node-based structure lives in memory. Once you are comfortable manipulating `next` pointers, **Stack (LIFO)** uses a linked list (or a dynamic array) as its underlying storage and adds a strict push, pop, peek discipline on top.
Not Started
0%
Matrix/Grid Fundamentals
Picture an image as a grid of pixels, a chessboard as an 8 by 8 array of squares, or a maze as a rectangle of walls and corridors. Each of these is a **Matrix/Grid**: a two-dimensional array indexed by `(row, column)` where the right traversal order can turn a hard problem into a single nested loop. This lesson covers how to allocate and initialize 2D arrays in JavaScript and Python, how row-major storage maps a `[i][j]` access to a contiguous memory offset, and the standard traversal patterns: row by row, column by column, and along the two diagonals. You will also practice boundary checks, the most common source of off-by-one bugs in grid problems, and implement a transpose to see how index swaps reshape data in place. In **Arrays & Strings**, you saw that a one-dimensional array gives `O(1)` indexed access through a single offset calculation. A grid is just nested arrays sharing that same property along two axes, so every shifting and bounds idea you used there carries over directly. Once you can move confidently through a grid, **Linked Lists (Singly)** shifts the focus to a very different memory layout: pointer-based nodes with no contiguous block at all.
Not Started
0%
Queue & Deque
BFS visits a graph one frontier at a time, and the trick that makes that work is a queue: nodes are added in the order they are discovered and pulled out in the same order, level by level. **Queue & Deque** captures that First In, First Out discipline as a reusable abstraction, then extends it to a double-ended variant that pops up surprisingly often in sliding-window problems. In this lesson, you will implement `enqueue`, `dequeue`, `front`, and `isEmpty` using both a linked list and an array, then see why a naive array queue wastes space and how a circular queue with two index pointers fixes that with `O(1)` operations. You will also build a deque that supports inserts and removes at both ends, the structure underneath the monotonic-deque pattern that solves sliding-window-maximum in linear time. In **Stack (LIFO)**, you implemented `push` and `pop` from the same end and saw how the choice of array versus linked list affected the worst-case bound. A queue uses the same two storage options but pulls from the opposite end, which is what forces the circular buffer trick when you back it with an array. With a queue in your toolkit, **Hash Map (Dictionary) Basics** introduces the most-used data structure in real-world code: a key-value store that answers lookups in expected `O(1)`.
Not Started
0%
Set Basics
Drop a million IDs into a `Set`, ask `set.has(id)` for any one of them, and the answer comes back in expected constant time without any ordering, sorting, or scanning. That single primitive (membership testing) is what makes a **Set** the quiet workhorse behind deduplication, visited-node tracking in graph traversal, and almost every cache-invalidation routine you will read. This lesson covers the uniqueness invariant of a set, the four core operations `add`, `delete`, `has`, and iteration in both JavaScript's `Set` and Python's `set`, and the four classical set-theory operations: union, intersection, difference, and symmetric difference. You will see why a set is implemented as a hash map whose values are unused, and you will practice the visited-set pattern that reappears in BFS, DFS, and cycle detection. In **Hash Map (Dictionary) Basics**, you learned that a hash function maps keys to bucket indices for `O(1)` get and set. A set keeps only the keys, drops the values, and exposes the resulting structure as a pure membership data type. Next, **Trees: Binary Tree Fundamentals** moves from flat unordered collections to hierarchical structures, where parent and child relationships unlock entirely new traversal patterns.
Not Started
0%
Stack (LIFO)
Every time a function calls another function, the language runtime pushes a frame onto an internal stack; every `return` pops one off. That same Last In, First Out discipline shows up in undo buttons, expression evaluators, browser back navigation, and the call patterns of recursion, which is why **Stack (LIFO)** is one of the first abstract data types every engineer learns. This lesson introduces the four core operations of a stack (`push`, `pop`, `peek`, `isEmpty`), the underflow case you must handle on every pop, and two implementations: a dynamic array stack with amortized `O(1)` push, and a linked list stack with strict `O(1)` push at the head. You will trace stack state through bracket matching, postfix evaluation, and string reversal so the LIFO mental model becomes a tool you reach for instinctively when a problem asks you to remember the most recent thing. In **Arrays & Strings**, you saw that appending to the end of a dynamic array is `O(1)` amortized, which is exactly the cost model for an array-backed stack. **Linked Lists (Singly)** showed that head insertion is unconditionally `O(1)`, which is what makes a linked list the cleanest backing store for a stack with a strict worst-case guarantee. Next, **Queue & Deque** flips the discipline: instead of pulling from the same end you pushed to, you pull from the opposite end, and the implementation gets just a little more interesting.
Not Started
0%
Algorithms
Backtracking (Intro)
Listing every subset of a 20-element set produces over a million results, and there is no shortcut: you have to enumerate them. The trick is to do it without writing 20 nested loops. Backtracking solves the entire family of "generate all configurations" problems with a single recursive template that walks an implicit decision tree. **Backtracking (Intro)** introduces that template as the "choose, explore, unchoose" pattern. You will see how each recursive frame picks an option, recurses into the resulting state, and then undoes the choice on the way back up so the next branch starts from a clean slate. Classic problems include generating all subsets and permutations, the N-Queens placement puzzle, and a first look at Sudoku. The lesson also introduces pruning, where invalid partial solutions are cut off early so the algorithm explores far less than the full `O(2^n)` or `O(n!)` worst case. In **Recursion Fundamentals**, you learned to define a problem in terms of smaller subproblems. Backtracking adds one more idea: state mutated during recursion has to be restored before returning, so siblings in the call tree see the same starting state. From here, **BFS & DFS (Intro)** generalizes the same tree-of-states view to actual graphs.
Not Started
0%
BFS & DFS (Intro)
Swap a queue for a stack inside a graph traversal and you flip from breadth-first to depth-first behavior. The visited-vertex tracking is identical, the neighbor expansion is identical, and yet one explores level by level while the other dives down the longest path first. That single data-structure swap is the entire difference between the two most-used graph algorithms in the field. **BFS & DFS (Intro)** walks through both. For BFS you will implement queue-based level-by-level exploration and use it to find shortest paths in unweighted graphs. For DFS you will see both the recursive form (which uses the call stack implicitly) and the iterative form with an explicit stack, and apply them to connectivity checks, path finding, and cycle detection. The lesson also covers the visited set that prevents infinite loops on cyclic graphs and contrasts graph traversal with tree traversal as a special case. This lesson assumes you are comfortable with three structures from the data-structures track: **Stack (LIFO)** powers iterative DFS, **Queue & Deque** powers BFS, and **Graphs: Representation Basics** gives you the adjacency list or matrix that both algorithms read from. Next, **Hashing Techniques** turns to a different building block, the hash map, which underpins many of the lookup-heavy patterns you will combine with traversal in later lessons.
Not Started
0%
Binary Search (Intro)
Searching a sorted array of one billion elements with linear search takes up to a billion comparisons. Binary search needs about thirty. The same input, the same hardware, two algorithms with completely different scaling behavior, separated only by the assumption that the array is sorted. **Binary Search (Intro)** explains how to get that speedup and how to write the algorithm without the off-by-one errors and infinite loops that trip up almost everyone the first time. You will implement the canonical two-pointer template with `left` and `right` indices, learn why `mid = left + (right - left) / 2` matters for avoiding integer overflow, and trace the algorithm step by step on small inputs to see the search space halve each iteration. The lesson also surveys built-in support across languages (`Array.indexOf` is _not_ a binary search; Python's `bisect` is) and the most common pitfalls. In **Arrays & Strings**, you saw why indexed access on an array is `O(1)`; binary search is the first algorithm that uses that property to skip past whole regions instead of walking through them. **Big-O Notation (Upper Bound)** gave you the language `O(log n)` and the reasoning that explains why halving leads to logarithmic growth. From here you will move into **Two Pointers (Intro)**, which generalizes the same coordinated-index idea beyond exact-match searching.
Not Started
0%
Hashing Techniques
Two Sum was a hard problem in 2010. With a hash map it becomes a six-line interview warm-up: walk the array once, and for each value `x` ask whether `target - x` is already in the map. The same shape, single-pass plus complement lookup, solves "first duplicate," "contains nearby duplicate," "longest substring without repeats," and an entire family of trade-space-for-time problems. **Hashing Techniques** catalogs those patterns. You will work through frequency counting (most/least frequent element, group anagrams via sorted-key trick), the two-sum complement template and its three-sum / four-sum extensions, existence checking and duplicate detection, index mapping where you store positions alongside values, and a first conceptual look at rolling hashes that prepare you for Rabin-Karp later. The lesson also discusses when a hash map is overkill versus when it is the obviously correct tool. In **Hash Map (Dictionary) Basics**, you saw how hashing achieves expected `O(1)` insertion and lookup. This lesson is where that constant-time primitive turns into algorithmic leverage: brute-force `O(n^2)` scans collapse into a single pass that uses the map as an oracle. From here you move into the paid track with **Sorting (Advanced)**, which trades hash-map lookups for divide-and-conquer to break the `O(n^2)` sorting barrier.
Not Started
0%
Introduction to Algorithms
Two recipes can both bake the same cake, yet one calls for thirty minutes and the other for three hours. The same is true of code: a problem like sorting a list or finding the largest number in an array has many valid solutions, and the choice between them is exactly what an algorithm captures. This lesson defines what an algorithm actually is, naming the five characteristics every algorithm shares (input, output, definiteness, finiteness, effectiveness). It separates the abstract steps of a method from the concrete code that implements it, and walks through how to judge whether a procedure is _correct_ before worrying about whether it is fast. You will also build a first informal sense of efficiency, the way work grows as inputs get bigger, without any formal notation yet. In **How to Read Code (JS & Python)**, you practiced tracing variables, loops, and function calls in two languages. **Introduction to Algorithms** zooms out from individual lines to the recipe as a whole, treating those traces as evidence that a method does what it claims. From here you will move into **Iteration Patterns on Arrays/Strings**, where these abstract ideas about correctness and efficiency get applied to concrete traversal templates.
Not Started
0%
Iteration Patterns on Arrays/Strings
Look at almost any solved interview problem and you will see the same six or seven shapes of `for` loop reappearing: a single sweep that finds a max, a nested pair that compares every element to every other, a single sweep that fills a frequency table. Before you can recognize when two pointers or sliding window will help, you have to recognize these primitive shapes on sight. **Iteration Patterns on Arrays/Strings** catalogs those shapes. You will work through single-pass templates (running sum, find max/min, counting), nested-loop templates that consider all pairs in `O(n^2)` time, frequency counting with a hash map, early termination with `break`, sentinel values, and the choice between in-place modification and building a new array. Classic transformations like reverse, rotate, and partition appear as named patterns rather than puzzles solved from scratch each time. In **Arrays & Strings**, you saw that arrays give you constant-time indexed access and contiguous memory. This lesson turns that storage model into actual movement: how an index walks across an array, what it costs, and when one walk is enough. Next you will use these patterns directly in **Prefix Sum & Difference Array**, where a single preprocessing pass replaces many later range-sum loops.
Not Started
0%
Prefix Sum & Difference Array
Imagine an array of a million numbers and a thousand questions of the form "what is the sum of elements between index `l` and `r`?". The naive answer loops through each query, doing roughly a billion additions in total. Prefix sums turn the same workload into a million additions plus a thousand subtractions, with answers in constant time per query. **Prefix Sum & Difference Array** covers the technique in three layers. You will build 1D prefix sums where `prefix[i] = prefix[i-1] + arr[i]` and answer any range sum in `O(1)`. You will then learn the inverse, the difference array, which turns repeated `O(n)` range updates into `O(1)` updates that you reconstruct in a single final pass. The lesson finishes with 2D prefix sums and the inclusion-exclusion trick for answering submatrix sum queries, plus quick variants like prefix XOR and prefix product. In **Arrays & Strings**, you saw why arrays support `O(1)` random access; that property is exactly what makes prefix lookup possible. **Iteration Patterns on Arrays/Strings** trained you to think in terms of single passes, and the prefix sum is the first pattern where one preprocessing pass replaces an entire family of later loops. From here you will move into **Sorting (Elementary)**, where rearranging the array unlocks an entirely different class of techniques.
Not Started
0%
Recursion Fundamentals
The mathematical definition `factorial(n) = n * factorial(n - 1)` defines a function in terms of itself, with `factorial(0) = 1` as the stopping point. That two-line definition is also a complete program if your language lets a function call itself. Recursion is what turns self-referential definitions into runnable code, and many problems (tree traversal, graph search, divide and conquer, dynamic programming) describe themselves most naturally that way. **Recursion Fundamentals** breaks down how that machinery actually works. You will learn to identify a base case (when the recursion stops) and a recursive case (how the problem reduces to a smaller instance), trace the call stack as frames are pushed and popped, and reason about return values flowing back up the chain. Examples run through factorial, naive Fibonacci, sum of an array, and integer power, plus a first look at recursion-tree intuition, tail recursion, stack overflow, and the `O(depth)` space cost of deep recursion. In **How to Read Code (JS & Python)**, you saw functions call other functions. **Debugging by Tracing** taught you to follow those calls step by step. Recursion is the case where the function on the call stack is the same function, again and again, with smaller inputs each time. Next, **Backtracking (Intro)** extends recursion with an explicit undo step to systematically explore combinatorial search spaces.
Not Started
0%
Searching (Basics)
If your phone's contact list is unsorted and you are looking for Sam, the only option is to read names top to bottom until you find a match or hit the end. That brute-force scan is linear search, and it is the right algorithm in a surprising number of situations: small arrays, unsorted data, linked lists, and any case where sorting first would cost more than the search itself. **Searching (Basics)** covers linear search and its small family of variations. You will implement the standard `O(n)` scan in JavaScript and Python, learn the sentinel trick that removes a per-iteration boundary check, and adapt the same template to find the first occurrence, the last occurrence, or the first element matching a condition. The lesson also walks through linear search on linked lists, where you cannot skip ahead by index. In **Arrays & Strings**, you saw why arrays support `O(1)` random access. Searching turns that primitive into a question: how many of those accesses do you need to find a value, and can you do better than reading every one? That question motivates **Binary Search (Intro)** next, which exploits sorted order to bring the cost down to `O(log n)`.
Not Started
0%
Sliding Window (Intro)
To find the longest substring of `s` with no repeated characters, the brute-force approach checks every substring in `O(n^3)` time. The sliding-window solution touches each character at most twice and runs in `O(n)`: extend a right pointer until a duplicate appears, then advance a left pointer until the duplicate is gone, repeating until the right pointer falls off the end. **Sliding Window (Intro)** turns that idea into two reusable templates. The fixed-size window slides a range of length `k` across the array and updates the running sum or count by adding the new right element and removing the old left element, never recomputing from scratch. The variable-size window expands the right edge while a condition holds and shrinks the left edge to restore that condition, tracking the window's contents with a hash map or counter. You will apply both to maximum-sum subarray of size `k`, longest substring without repeating characters, minimum window substring, and longest substring with at most `k` distinct characters. In **Two Pointers (Intro)**, you used coordinated indices to walk an array linearly. **Hash Map (Dictionary) Basics** gave you the `O(1)` lookup and update that lets a window track its own contents efficiently. Sliding window combines both: two indices and one hash map. From here you turn to **Recursion Fundamentals**, which trades sequential index movement for self-similar subproblems.
Not Started
0%
Sorting (Elementary)
Insertion sort is `O(n^2)` and quick sort is `O(n log n)`, yet V8 actually runs insertion sort on arrays shorter than ten elements because the constant factors are smaller. The takeaway is that elementary sorts are not just historical curiosities; they are the algorithms that real engines drop down to once an array is small enough. **Sorting (Elementary)** walks through bubble sort, selection sort, and insertion sort in detail. For each, you will trace the pass-by-pass behavior on a small input, count comparisons and swaps, and analyze worst, best, and average time complexity. Along the way the lesson introduces the vocabulary that every later sorting topic relies on: passes, the growing sorted region, the loop invariant that proves correctness, in-place vs. extra-space sorts, and stability (whether equal keys keep their original relative order). In **Arrays & Strings**, you learned how arrays store and index data; sorting is the first time you will rearrange that storage in place. **Iteration Patterns on Arrays/Strings** trained you in nested loops and swaps, and these three sorts are essentially structured nested loops with a clear invariant. Up next, **Searching (Basics)** establishes the linear-search baseline whose `O(n)` cost is exactly what binary search will later cut down once your data is sorted.
Not Started
0%
Two Pointers (Intro)
Given a sorted array and a target sum, the brute-force "check every pair" approach is `O(n^2)`. With two indices walking inward from opposite ends, the same problem drops to `O(n)`: if the current pair sums too low, move the left pointer right; if it sums too high, move the right pointer left. **Two Pointers (Intro)** turns that insight into a reusable pattern with two main shapes. The opposite-direction variant has pointers converge from both ends and powers problems like pair sum, palindrome check, reverse in place, and container with most water. The same-direction (fast and slow) variant has both pointers walk forward at different speeds and powers in-place filtering problems like remove duplicates from a sorted array, move zeros to the end, and the linked-list cycle detection you will revisit later. In **Arrays & Strings**, you learned how a single index walks across an array. This lesson coordinates two indices in a way that exploits sorted order or a structural invariant to skip work an inner loop would otherwise repeat. Next, **Sliding Window (Intro)** generalizes the same-direction pointer pair into an expanding and shrinking range that solves contiguous-subarray problems.
Not Started
0%
Practice Problems
Add Binary
Given two binary strings, return their sum as a binary string.
Missing Number
Given an array of n distinct numbers from the range [0, n], find the one number that is missing from the array.
Reverse Bits
Reverse all 32 bits of a given unsigned integer and return the result as an unsigned integer.
System Design
CAP Theorem & Trade-offs
The CAP theorem says any distributed data store must trade off Consistency, Availability, or Partition tolerance during a network split, and you only get to keep two. This lesson cuts through the textbook version with the practical engineer's reading: partitions are non-negotiable, so the real choice is between consistency and availability when the network breaks. We cover what each property actually means, why CAP is misleading without PACELC, and how real systems (MongoDB, DynamoDB, Cassandra, Spanner) place themselves on the spectrum. By the end you can defend a system's CAP choice in an interview without falling into the common 'I picked CA' trap.
Consistency Models (Strong, Eventual, Causal)
Consistency models are the contract between a distributed data store and its clients about what they can and cannot observe. This lesson walks the spectrum from strict serializability at the strong end to eventual consistency at the relaxed end, with stops at linearizability, sequential, causal, read-your-writes, monotonic reads, and monotonic writes. We focus on what each model promises, what bugs it prevents, what it costs in latency and availability, and which production systems implement it. By the end you can name the model your system needs and explain why - the senior-level move that interviewers reward.
Consistent Hashing & Data Distribution
Consistent hashing is the trick that lets distributed caches and databases add or remove nodes without remapping every key in the cluster. This lesson explains why naive `hash(key) % N` is broken, how the hash ring works, why you need virtual nodes to keep load balanced, and how real systems (DynamoDB, Cassandra, Memcached, Discord) implement it. We finish with the modern alternatives (rendezvous hashing, jump consistent hash, Maglev) and the trade-offs that make consistent hashing the answer in interviews 90% of the time.
Message Queues (Kafka, RabbitMQ, SQS)
Message queues let one service hand work to another without waiting, smoothing traffic spikes, decoupling services, and surviving downstream outages. This lesson covers the two queue families (broker-based like RabbitMQ and SQS vs log-based like Kafka), the delivery semantics (at-most-once, at-least-once, exactly-once), the operational essentials (DLQs, consumer groups, backpressure, ordering), and the trade-offs that decide between Kafka, RabbitMQ, and SQS for any given workload. By the end you can pick a queue and defend the choice with the per-property reasoning interviewers reward.
Fault Tolerance, Redundancy & Failover
Fault tolerance is the property that lets a system keep working when components fail - and at any reasonable scale, components are always failing. This lesson covers the building blocks: redundancy (active-active, active-passive), failure detection (health checks, heartbeats), failover (automatic, manual), and the patterns that make systems gracefully degrade instead of catastrophically crash (circuit breakers, retries with backoff, bulkheads, timeouts). We finish with the operational disciplines that turn architecture into reality: chaos engineering, runbooks, blast-radius analysis, and disaster recovery (RTO/RPO). By the end you can design a system that survives the failure modes interviewers love to throw at you.
Design a URL Shortener (TinyURL)
Design a URL shortening service like TinyURL or bit.ly that maps a long URL to a 7 character code, redirects clicks in under 50 ms, and survives a 100:1 read-to-write ratio. This lesson walks through capacity estimation, the choice between counter based and hash based key generation, the database split between a key store and an analytics store, and the caching strategy that lets a single mid-tier service handle 10K redirects per second on commodity hardware.
Design Pastebin
Design a service like Pastebin or GitHub Gist where users dump up to 10 MB of text and share a link. The interview twist over a URL shortener: pastes are big, so you store them in object storage (S3) and only keep metadata in your database. This lesson covers the metadata vs blob split, expiration via S3 lifecycle policies, presigned URLs for direct uploads, syntax highlighting strategy, and how to handle the read pattern when most pastes are read once and never again.
Design Instagram (Photo Sharing)
Design a photo sharing service like Instagram with 500M daily active users uploading 100M photos a day, served as personalized feeds at sub-200 ms p99. The interview centerpiece is the news feed: fan-out on write versus fan-out on read, the celebrity problem, and the hybrid pull-on-read model that real Instagram uses. We also cover photo upload pipelines (presigned URLs, multi-resolution generation, CDN), the metadata data model, and how to scale follow graphs that go from a few friends to hundreds of millions of followers.
Design a Chat System (WhatsApp)
Design a real-time chat system like WhatsApp serving 2B users sending 100B messages per day with sub-second delivery, presence indicators, and read receipts. The interview centerpiece is the persistent WebSocket connection layer: how many connections per server, how to route a message to a recipient who may be on a different server, and how to guarantee delivery when the recipient is offline. We cover the message delivery state machine (sent, delivered, read), the connection routing layer that maps user_id to a chat server, the message store for offline delivery, and presence/typing indicators that operate at a higher write rate than messages themselves.
Design Typeahead / Autocomplete
Design a typeahead/autocomplete service like Google Search's suggestion bar that returns the top 10 ranked completions for a query prefix in under 100ms p99, scaling to 5B searches per day with a multi-billion-entry suggestion index. The interview centerpiece is the data structure choice (trie vs sorted strings vs ngram index) and the offline pipeline that ranks suggestions by frequency, recency, personalization, and click-through rate. We cover the trie with precomputed top-K per node, edge n-gram indexes for typo tolerance, the MapReduce/Spark batch pipeline that rebuilds suggestions nightly, and the per-region edge cache that absorbs 99% of traffic.
Design a Rate Limiter
Design a distributed rate limiter that protects an API platform from abuse and uneven load while staying fast and accurate at 1B requests per day. The interview centerpiece is choosing among the five canonical algorithms (fixed window, sliding window log, sliding window counter, token bucket, leaky bucket) and explaining how to make the chosen one atomic across a Redis cluster. We cover where to place the limiter (edge, gateway, in-process), per-IP vs per-user vs per-API-key keys, returning 429 with Retry-After, the hot key problem, and fail-open vs fail-closed under cache outages.
