Foundations
foundations
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%
Amortized Analysis
Appending to a Python list or JavaScript array almost always feels instantaneous, yet every so often the structure allocates a fresh buffer and copies every element into it, an `O(n)` operation. So why do textbooks still call append `O(1)`? Because amortized over a long sequence of appends, the rare expensive operation is paid for by many cheap ones. **Amortized Analysis** is the formal machinery that makes this kind of argument rigorous. This lesson teaches the three classical methods. The **aggregate method** sums the total cost of `n` operations and divides by `n` for a per-operation bound. The **accounting (banker's) method** charges each operation a fixed amount and stores credits on the data structure to pay for future expensive ones. The **potential (physicist's) method** defines a potential function `Phi` and computes amortized cost as actual cost plus the change in `Phi`. You will apply all three to dynamic array doubling, stacks with multipop, and binary counters, and see how amortized `O(1)` differs from worst-case `O(1)` and from average-case analysis. This lesson stands on the shoulders of **Big-O Notation (Upper Bound)** and **Time Complexity Analysis Techniques**. From those lessons you can already analyze a single operation; amortized analysis is what you reach for when the cost of *one* operation is misleading and the right object of study is a *sequence* of operations. Next you will move to **Recurrence Relations**, the other major analysis tool, used when an algorithm's cost is most naturally written recursively rather than as a running total.
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%
Basic Proof Techniques
Goldbach's conjecture has been verified for every even integer up to 4 * 10^18, yet mathematicians still call it a conjecture because no amount of testing covers every case. Algorithm correctness has the same shape: a function can pass a hundred unit tests and still fail on the empty list or the input size your tests never reached. **Basic Proof Techniques** equips you with the small set of argument styles that turn intuition into certainty about correctness, complexity bounds, and data structure invariants. Direct proof chains definitions and prior facts to a claim. Proof by contradiction assumes the opposite and derives a logical impossibility, as in the irrationality of `sqrt(2)` and the `Omega(n log n)` lower bound for comparison sorts. Mathematical induction, in weak and strong forms, proves statements over the natural numbers via a base case and an inductive step, the backbone of recurrence solutions and loop invariants. Proof by contraposition swaps `P implies Q` for the equivalent `not Q implies not P`, and proof by cases breaks a claim into exhaustive subcases. This lesson builds on **Discrete Mathematics Basics**, where you met sets, propositional logic, implications, and the first taste of formal proof. Here those informal sketches become the disciplined patterns that algorithm analysis, theoretical CS, and clean engineering arguments all rely on. With proofs in hand, you have completed the foundations track and are ready to step into **Arrays & Strings** and the rest of the data structures curriculum, armed to analyze every operation rigorously.
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%
Complexity Classes (Conceptual Overview)
Some problems have algorithms that finish in seconds on inputs of size a million. Others, like finding the shortest tour through 30 cities, can defeat the world's fastest computers no matter how cleverly you code. The boundary between these two worlds is studied formally in **complexity theory**, and the rough map of that boundary is given by **complexity classes**: `P`, `NP`, `NP-Complete`, and `NP-Hard`. This lesson introduces those classes conceptually. You will see how `P` collects problems solvable in polynomial time, how `NP` collects problems whose solutions can be *verified* in polynomial time (even if finding them might be slow), and how `NP-Complete` and `NP-Hard` capture the hardest problems in `NP` and beyond. You will meet famous examples (Traveling Salesman, SAT, the knapsack problem), get a light introduction to **polynomial-time reductions** as the tool used to prove a new problem is `NP-Complete`, and confront the legendary `P vs NP` question along with why it matters for cryptography and the entire structure of computer science. This lesson assumes you are fluent with all the basic asymptotic machinery from **Asymptotic Analysis Fundamentals**, **Big-O Notation (Upper Bound)**, **Time Complexity Analysis Techniques**, **Space Complexity Fundamentals**, and **Little-o and Little-omega Notations**. Complexity classes use those same growth-rate ideas to draw lines between "polynomial" and "exponential" rather than between specific functions like `n^2` and `n log n`. From here you will move into **Amortized Analysis**, shifting from broad problem classification back to the fine-grained per-operation analysis that complex data structures demand.
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%
Little-o and Little-omega Notations
When a paper claims an algorithm runs in `O(n^2)`, that statement leaves a question wide open: does the algorithm *actually* take quadratic time, or could the bound be loose? **Little-o** and **little-omega** are the notations that close that gap. `f(n) = o(g(n))` says `f` grows *strictly* slower than `g`, never matching its rate, and `f(n) = omega(g(n))` says `f` grows strictly faster. They are the precision tools of theoretical CS. This lesson defines both notations using the formal limit-based criterion: `f(n) = o(g(n))` iff `lim f(n)/g(n) = 0`, and `f(n) = omega(g(n))` iff `lim f(n)/g(n) = infinity`. You will prove and disprove `o` and `omega` claims for common function pairs (`n` vs `n^2`, `log n` vs `n`, `n` vs `n log n`, `2^n` vs `n^k`), use these notations to rank growth rates strictly, and see how the full five-notation system fits together: `O` and `Omega` allow equality, `Theta` requires it, while `o` and `omega` forbid it. This lesson builds on three earlier asymptotic foundations. From **Big-O Notation (Upper Bound)** you have non-strict ceilings; from **Big-Omega Notation (Lower Bound)** you have non-strict floors; and from **Big-Theta Notation (Tight Bound)** you have the exact-rate case. Little-o and little-omega simply require the inequality to be strict, ruling out the matching case that Big-O and Big-Omega permit. With all five notations under your belt, you will be ready for **Complexity Classes (Conceptual Overview)**, where these comparisons get used to separate classes like `P` and `NP`.
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%
Modular Arithmetic
Open any production hash table, any block cipher, any RSA implementation, or any competitive programming problem that ends in "output the answer modulo `10^9 + 7`", and you will find the same machinery underneath: **modular arithmetic**, the math of working with remainders rather than full integers. It is the bridge between number theory and a huge swath of real algorithms. This lesson develops that machinery from first principles. You will revisit the `mod` operation precisely (including its relationship to division and remainders), then prove and use the addition, subtraction, and multiplication rules that let you take `mod` at every step of a long calculation without changing the answer. From there you will implement **fast modular exponentiation** in `O(log n)` time using binary exponentiation, compute **modular inverses** with **Fermat's Little Theorem** when the modulus is prime, and see exactly why hash tables, RSA, and contest problems rely on these tricks to avoid overflow and to invert multiplications. This lesson builds on **Number Theory Fundamentals**, where you learned about primes, divisibility, `gcd`, and the Euclidean algorithm. Modular arithmetic uses all of those: primality lets you apply Fermat, `gcd` decides when an inverse exists, and divisibility underlies every congruence. From here the only foundations topic remaining is **Basic Proof Techniques**, where you will learn the proof tools (induction, contradiction, contraposition) that make the kinds of arguments you have just been waving at fully rigorous.
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%
Recurrence Relations
When you stare at merge sort or binary search, the running time is not naturally a simple sum like `n^2/2 + n/2`. It is something more interesting: the cost of a problem of size `n` is built out of the cost of smaller problems plus a little glue. That self-referential structure is captured by a **recurrence relation**, an equation of the form `T(n) = ...` written in terms of `T` on smaller inputs. This lesson teaches you to spot, write, and classify recurrences directly from recursive code. You will identify the two ingredients every recurrence needs (the recursive case and the base case), then translate familiar algorithms into their `T(n)` equations: factorial gives `T(n) = T(n-1) + 1`, binary search gives `T(n) = T(n/2) + 1`, merge sort gives `T(n) = 2 T(n/2) + n`, and naive Fibonacci gives `T(n) = T(n-1) + T(n-2) + 1`. You will also group recurrences into three families (linear, divide-and-conquer, and multi-branch) and recognize the Big-O class each typically produces. This lesson builds on **Big-O Notation (Upper Bound)**, which gave you the language for expressing complexity, and on **Mathematical Sequences**, which gave you experience with arithmetic, geometric, and Fibonacci-style sequences, exactly the patterns you will see when a recurrence is expanded. Writing a recurrence is half the battle; the other half is converting it into a closed-form Big-O. That is the subject of the very next lesson, **Solving Recurrence Relations**.
Not Started
0%
Solving Recurrence Relations
Writing the recurrence `T(n) = 2 T(n/2) + n` for merge sort is the easy part. Turning it into the closed-form `O(n log n)` is where the real algorithm analysis happens, and it is exactly what this lesson is for. **Solving Recurrence Relations** gives you a small toolkit that, used well, can analyze nearly every recursive algorithm you will meet in interviews, papers, or production. This lesson walks through five methods. The **Substitution Method** has you guess the solution and prove it by induction, useful when you already suspect the bound. The **Recursion Tree Method** visualizes work level by level and sums it; this is how you see why merge sort spends `n` work per level over `log_2(n)` levels. The **Master Theorem** mechanically solves the divide-and-conquer form `T(n) = a T(n/b) + f(n)` via three cases, and you will memorize when each case fires. The **Extended Master Theorem** patches the gap when `f(n)` includes logarithmic factors. The **Akra-Bazzi Method** generalizes to unequal subproblem sizes that the basic theorem cannot handle. You will practice picking the right tool for each recurrence rather than blindly reaching for the same one every time. This lesson directly continues **Recurrence Relations**, where you learned to write `T(n)` from recursive code, and reuses ideas from **Big-O Notation (Upper Bound)** and **Mathematical Sequences** for the underlying summation arguments. With the analysis sub-track complete, you will pivot back to advanced math in **Modular Arithmetic**, where number-theoretic tools meet algorithm design.
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
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%
