- Introduction
- Chapter 1 Learning to Read and Write Proofs
- Chapter 2 Propositional Logic and Reasoning Patterns
- Chapter 3 Predicate Logic, Quantifiers, and Formal Specification
- Chapter 4 Sets, Functions, and Relations
- Chapter 5 Core Proof Techniques: Direct, Contradiction, Induction, and Invariants
- Chapter 6 Integers and Modular Arithmetic
- Chapter 7 Counting Principles and the Pigeonhole Principle
- Chapter 8 Combinatorics: Permutations, Combinations, and the Binomial Theorem
- Chapter 9 Recurrences and Divide-and-Conquer Analysis
- Chapter 10 Generating Functions and Combinatorial Identities
- Chapter 11 Discrete Probability for Computer Science
- Chapter 12 Graphs: Models, Representations, and Basic Properties
- Chapter 13 Trees, Traversals, and Data-Structure Applications
- Chapter 14 Connectivity, BFS/DFS, and Shortest Paths
- Chapter 15 Matchings, Flows, and Cuts
- Chapter 16 Planarity, Coloring, and Structure Theorems
- Chapter 17 Boolean Algebra and Logic Circuits
- Chapter 18 Automata and Formal Languages
- Chapter 19 Turing Machines and Computability
- Chapter 20 Reductions, Unsolvability, and the Limits of Computation
- Chapter 21 Asymptotics and Time–Space Complexity
- Chapter 22 Complexity Classes: P, NP, coNP, and Beyond
- Chapter 23 NP-Completeness: Proofs and Practical Implications
- Chapter 24 Randomization, Hashing, and the Probabilistic Method
- Chapter 25 Foundations of Cryptography: Primes, Primality, and RSA
Foundations of Computer Science: Discrete Math and Theory Made Clear
Table of Contents
Introduction
Computer science is built on a bedrock of discrete structures and precise reasoning. Whether you are designing a secure protocol, analyzing the running time of an algorithm, or verifying a system’s behavior, you rely on ideas that are fundamentally mathematical: logic, combinatorics, graphs, probability, and formal models of computation. This book, Foundations of Computer Science: Discrete Math and Theory Made Clear, aims to make those ideas intuitive without sacrificing rigor. We emphasize insight first and formalism immediately after, so that every definition and theorem earns its keep through compelling examples and practical applications.
The intended audience includes students encountering discrete mathematics and theoretical computer science for the first time, as well as professionals who want to refresh core concepts before tackling advanced work in algorithms, cryptography, machine learning theory, or formal verification. You will find a careful, self-contained development of the essentials, accompanied by step-by-step proofs and problem-solving strategies you can reuse. The material assumes comfort with high-school algebra and basic programming; calculus is not required.
What sets this book apart is its sustained connection to algorithmic thinking and security-motivated examples. Induction is introduced alongside recursion and loop invariants; combinatorial counting is tied to hashing schemes and data-structure analysis; graph concepts appear through shortest paths, network connectivity, flows, and matchings; and modular arithmetic leads naturally to modern cryptographic mechanisms like RSA. Along the way, we highlight how discrete probability informs randomized algorithms and how formal language theory and Turing machines clarify what computers can and cannot do.
Our pedagogical approach balances intuition, visual reasoning, and formal proof. Each chapter opens with motivating questions, builds a precise vocabulary of definitions, and develops theorems with proofs that model clear mathematical writing. Worked examples illustrate common proof techniques—direct arguments, contradiction, contrapositive, induction, and invariant-based reasoning—and “debugging the proof” sidebars expose typical pitfalls. Where helpful, diagrams and small experiments (often in pseudocode) connect abstract concepts to computational practice.
To help you learn actively, chapters include “quick checks” for immediate feedback, progressively challenging exercises, and application problems that synthesize multiple ideas. Many problems are designed to mirror the way concepts appear in real systems: analyzing the complexity of an algorithmic variant, reasoning about the correctness of a protocol, or modeling a network with graph-theoretic tools. Suggested solution outlines emphasize strategy: how to choose an appropriate proof technique, how to craft a reduction, and how to recognize a hidden invariant.
The book is organized to support both one- and two-semester courses. Early chapters establish proof literacy, logic, sets, and relations; the middle third develops combinatorics, recurrences, generating functions, probability, and graph theory; the final third treats models of computation, computability, and complexity, culminating in NP-completeness and the probabilistic method, with a capstone chapter on the mathematics underpinning cryptography. While the chapters build on each other, most include optional sections marked for deeper exploration, allowing instructors and readers to tailor the path.
By the end, you should be comfortable reading and writing proofs, translating informal reasoning into precise statements, analyzing algorithmic complexity with confidence, recognizing when a problem is likely intractable, and applying discrete mathematical tools in areas such as data structures, networks, randomized algorithms, and cryptography. More importantly, you will have developed a way of thinking—precise, modular, and reductionist—that scales to new problems and new technologies. Theory made clear is theory you can use; our goal is to make it part of your everyday toolkit as a computer scientist.
CHAPTER ONE: Learning to Read and Write Proofs
Mathematics is a language with a strict grammar and a surprisingly flexible vocabulary. For computer scientists, it is the lingua franca we use to state what an algorithm should do, to argue that it does so correctly, and to measure how efficiently it runs. Learning to read and write proofs is not about memorizing arcane rituals; it is about learning to build convincing, checkable arguments. A proof is a controlled chain of reasoning that leaves no room for ambiguity, yet it should feel like a story with a clear beginning, a logical progression, and a satisfying end. The discipline of writing proofs trains your mind to spot hidden assumptions, to organize complex ideas into modular steps, and to defend your claims against the most stubborn skeptic.
A proof is a bridge from axioms and definitions to theorems. On one side, you have the foundational truths you are allowed to use: the axioms of the system, the definitions you have adopted, and previously proven theorems. On the other side, you have the statement you wish to establish. The proof constructs that bridge with valid logical steps. In a mathematical argument, you do not get to rely on intuition alone, nor on a single numerical example, because those do not guarantee universality. Instead, you provide a rigorous chain of reasoning that holds for every object satisfying the hypotheses. If the chain is sound, the destination is unavoidable, no matter how surprising it may seem at first.
The precise meanings of words in mathematics are essential because small changes in phrasing can alter the meaning entirely. In everyday language, “if” and “only if” are sometimes used interchangeably, but in mathematics they are distinct. The statement “P only if Q” means that whenever P is true, Q must also be true, which is logically equivalent to “if Q is false, then P is false.” The statement “P if Q” means that Q implies P. The combined statement “P if and only if Q” asserts that P and Q always have the same truth value, and a proof of such a claim requires establishing both directions. Precision in language is not pedantry; it is the guardrail that keeps your reasoning on the road.
Before you can prove a claim, you must understand exactly what it asserts. This often starts by translating the claim into a clearer form or by writing out the definitions it invokes. Consider a classic: “Every even integer greater than two can be expressed as the sum of two primes.” To even begin, you need to know what an even integer is, what a prime is, and what it means to be expressed as a sum. The same care applies to statements about algorithms: “The algorithm sorts the input list” is vague until you specify that the output is a permutation of the input and is in nondecreasing order. The act of making vague claims precise often reveals the heart of the problem and suggests the structure of the proof.
Definitions are the bricks of mathematical architecture. They give you objects to work with and properties you may assume. A function f from the set of integers Z to itself is a rule that assigns each integer n an integer f(n). It is injective if f(a) = f(b) implies a = b; it is surjective if for every y in Z there exists some x in Z with f(x) = y; it is bijective if it is both injective and surjective. None of these notions is mysterious once written down carefully. The good student of proofs learns to pause at each definition: what are the assumptions, what is the conclusion, and how do I manipulate the assumptions to reach the conclusion? That pause is often the difference between a correct proof and a near miss.
Definitions are not the only tools; axioms and previously proven theorems are the scaffolding you are permitted to use. In discrete mathematics, we take as axioms the basic properties of equality, the familiar rules of arithmetic for integers, and the laws of logic. Later, we will adopt set-theoretic axioms as needed. You should never introduce new properties out of thin air. It is tempting to argue that a claim is “obvious” because it matches your experience with small examples, but general validity requires a justification that does not depend on a particular size or instance. At the same time, clever examples play a crucial role: a single counterexample can demolish a conjecture, and exploratory testing often illuminates why a claim is true.
Suppose someone claims that every prime number is odd. This looks plausible if you think of 2, 3, 5, and so on, but a single number, 2, provides a counterexample that immediately disproves the claim. A counterexample is a complete argument on its own. It does not need to be elaborate; it needs to satisfy the hypotheses and violate the conclusion. The skill here is to choose a case that tests the boundary of the statement. If the statement involves “all” objects, ask if there is an exceptional object. If it involves “there exists” an object, try to construct one. The ability to produce or hunt for counterexamples is a first line of defense against flawed reasoning.
Direct proofs are often the simplest and most illuminating. To prove that the sum of two even numbers is even, you take two arbitrary even integers, say a and b. Since a is even, there exists an integer k such that a = 2k. Similarly, b = 2m for some integer m. Then a + b = 2k + 2m = 2(k + m). Because k + m is an integer, a + b is divisible by 2, so it is even. This argument works for all even numbers because it relies only on the definition of evenness and the fact that integers are closed under addition. The key move is to pick the arbitrary objects up to the definition, perform standard operations, and show that the conclusion follows inevitably. No intuition is required beyond the definitions and the rules of arithmetic.
Not every claim is best met head-on. When the conclusion is a conditional statement of the form “if P, then Q,” you sometimes need a different strategy. One common approach is the contrapositive: instead of proving “P implies Q,” prove “not Q implies not P.” These two statements are logically equivalent. For example, to prove that if the product ab is even, then at least one of a or b is even, you could start by assuming both a and b are odd and show that their product must be odd. Since odd times odd is odd, the contrapositive holds, and so does the original statement. The contrapositive is especially useful when the hypothesis provides a negative property or when assuming the negation of the conclusion gives you a concrete starting point.
A proof by contradiction assumes the negation of what you want to prove, and then derives a logical impossibility or a violation of a known fact. This technique is not a way to escape careful reasoning; it simply uses the fact that a statement and its negation cannot both be true. For instance, to show that there are infinitely many prime numbers, one assumes that there are only finitely many, constructs from them a number that has a prime factor not on the list, and reaches a contradiction. In computer science, contradictions often arise from resource constraints, such as assuming that a certain algorithm always halts within a given time bound but then showing that it would need more steps than there are atoms in the universe, which is impossible given the input size. Contradiction is a powerful hammer, but you should not use it when a direct nail is available.
Proof by cases is useful when an object falls into distinct categories with different properties. You prove that the conclusion holds in each case separately, and since the cases cover all possibilities, the conclusion holds universally. Consider proving that the product of any two integers with the same parity is even. If both are even, the product is even. If both are odd, the product is odd—wait, that would be a mistake. A correct claim might be that the product of any two integers with the same parity is even if at least one is even, or perhaps better: the product of any two odd integers is odd, but that does not show evenness. Better still, prove: for any integers a and b, if a is even or b is even, then ab is even. Split into cases: case 1, a is even; case 2, b is even. In each, use the definition of even. The structure is not glamorous, but it is reliable and makes your argument modular and easy to check.
Definitions often involve existence statements: “there exists an x such that...” A constructive proof provides such an x explicitly, often by describing a method to find it. For example, to show that for any integer n there exists a multiple of n that consists only of digits 0 and 1, you can construct a sequence of numbers: 1, 11, 111, and so on. Consider the remainders of these numbers modulo n. By the pigeonhole principle, two of them must share the same remainder. Subtracting the smaller from the larger yields a number consisting only of 0s and 1s that is divisible by n. Constructive proofs often have the added benefit of being algorithms. They show not only that an object exists but how to find it. This dual nature makes them especially valuable in computer science, where existence is often synonymous with computability.
An existence proof does not have to be constructive. Sometimes you can show that an object exists by counting arguments or by applying a general principle without giving an explicit example. The probabilistic method is a famous nonconstructive tool: to prove that a certain object with desired properties exists, you show that a random choice produces one with positive probability. For instance, there exist graphs with high chromatic number and no short cycles, a fact that can be established via probability even though constructing such graphs explicitly is difficult. In theoretical computer science, such arguments show that solutions exist even when finding them is hard. That said, nonconstructive proofs can be unsatisfying if what you really want is an algorithm, and they motivate the search for constructive methods.
Quantifiers govern the scope and order of existence and universality. The difference between “for all x there exists a y” and “there exists a y such that for all x” is dramatic. The first says that for each x you can pick a y, and the y may depend on x. The second says there is a single y that works for every x simultaneously. In analysis of algorithms, this often shows up as: for every input size n, there exists a constant c such that running time T(n) ≤ c n^2. This means the constant may depend on the algorithm but not on n. If the quantifiers were reversed, the statement would be nonsense. A proof must respect the order and meaning of quantifiers, and reading them carefully is as important as reading the words themselves.
Many computer science proofs rely on invariants—properties that remain true throughout a computation. To prove that a loop terminates with a correct result, you identify a loop invariant that is true before the loop starts, preserved by each iteration, and sufficient to imply correctness when the loop ends. For example, in a loop computing the greatest common divisor of two numbers, the invariant might be that the gcd of the current pair equals the gcd of the original pair. A proof by induction also has an invariant flavor, but over the natural numbers: you establish a base case and show that truth for n implies truth for n+1. The power of induction is that it pushes a base truth along an infinite chain of implications, proving a claim for all natural numbers.
When writing a proof, clarity is a virtue. Start with a plan: what is the goal, what tools are available, and what strategy will you use? Announce your strategy: “We will prove the contrapositive,” or “We proceed by induction on n.” Define any nonstandard terms. Avoid pronouns without clear antecedents. Use “we” to guide the reader through the proof: “We now show that...” Keep steps short, and check that each follows from the previous by a known rule. A reader should be able to verify each step without having to guess what you meant. A good proof reads like a well-documented program: modular, with clear preconditions and postconditions for each section.
To practice reading proofs, it helps to classify them by technique and to collect patterns. A direct proof often mirrors a function composition: from the definitions, perform algebraic manipulations to reach the goal. A contrapositive flips the direction and often simplifies assumptions. A proof by cases divides the problem into smaller, independent tasks. Induction decomposes an infinite set into a base and a step. Invariants maintain a property during a process. Recognizing the pattern helps you anticipate the structure, which in turn helps you fill in the details. When studying a proof, try to extract the “skeleton” first: what is the high-level idea, and where does the machinery engage? Then dive into the details.
Definitions can be subtle, especially when they involve negation. A number is composite if it can be written as a product of two integers greater than 1. The negation is not “cannot be written as a product of two integers,” since any integer can be written as a product, e.g., n = n * 1. The correct negation is “every factorization of n into two integers must have one factor equal to 1 or -1.” That nuance matters. Similarly, “at least one” is not the same as “exactly one.” The phrase “not all” is equivalent to “there exists some that does not.” Misreading these negations can lead you to prove the wrong statement. Practice translating a statement into its negation; if you can state clearly what you are trying to disprove, you are halfway to proving what you want.
Sometimes the difficulty is not the proof itself but that the claim is false or misstated. When a proof stalls, question the definitions. Are you assuming commutativity where it does not hold? Are you treating integers as if they were real numbers? In graph theory, a small change like “undirected” versus “directed” or “simple” versus “multigraph” can flip the truth of a statement. In algorithm analysis, an assumption about the input being sorted can make an O(n) algorithm possible, whereas unsorted input may force O(n log n). The quickest way to fix a faulty proof is often to fix the claim: narrow the hypothesis or strengthen the conclusion to match the intended intuition.
Concrete examples are invaluable for building intuition, but you must generalize carefully. If you want to prove a statement about all even integers, testing 2, 4, and 6 will not hurt, but it will not suffice as proof. However, a pattern observed in examples can suggest a proof strategy. For instance, if you notice that powers of two modulo three alternate between 1 and 2, you might suspect a recurrence and try to prove it by induction. Examples can also reveal counterexamples. If a conjecture fails for n = 2, you are done. The interplay between exploration and rigor is like debugging: you gather evidence, form a hypothesis, then write a test that either confirms it or pinpoints a failure.
Consider a claim that appears algorithmic: “There exists a strategy to play a game such that the player always wins.” To prove existence, you might try to construct the strategy explicitly. Alternatively, you might use an adversarial argument: show that if no such strategy existed, the opponent could force a win, which would contradict a known result. In combinatorial game theory, this style of argument is common. The proof moves from global properties to local decisions, often inverting the perspective. This kind of reasoning is a good example of how existence and impossibility proofs can be entangled, and why clarity of logical structure is essential.
A proof is often a dialogue with the reader. You must anticipate their doubts and address them. One way to do this is to explain why each step is justified. Another is to provide small motivational remarks without disrupting the flow: “At this point we need a property of modular arithmetic: ...” However, resist the temptation to fill the page with rambling commentary. Brevity and clarity are allies. If a step feels mysterious, break it into two substeps. If an algebraic manipulation is complicated, add a sentence explaining the goal of that manipulation. When you are done, reread your proof as if you were a skeptical stranger and try to poke holes in it.
A common mistake is to reason from the conclusion backward to the hypothesis without ensuring that the steps are reversible. For example, you might start with the equation you want to prove and manipulate it until you reach a known fact. If each step is reversible, this is fine. If not, you may be assuming what you want to prove. A safer method is to build a forward chain from assumptions to conclusion. When you do work backward, note which steps are equivalences and which are one-way implications. Another pitfall is hidden reliance on a special case, such as assuming a variable is nonzero when the definition allows zero, or assuming an array is sorted when the claim should hold for all arrays.
A good way to strengthen your proof-reading skill is to study bad proofs. Take a plausible but incorrect argument and find the precise step where it fails. Is it a misquoted definition? A quantifier swapped? A hidden assumption that only holds for small numbers? Once you locate the faulty move, try to repair it by either changing the claim or tightening the reasoning. This process is analogous to debugging code: isolate the bug, understand why it occurs, and patch it with a precise fix. Over time, you will develop a mental checklist of common errors and become faster at spotting them in your own work.
Proofs can be aesthetically pleasing, but beauty is not the goal; correctness and clarity are. Elegant proofs are often short because they skip over tedious but necessary verifications. When learning, do not be afraid to include the tedious parts. If you need to show that a particular number is an integer, explicitly justify why a division yields an integer. If a property follows from an earlier theorem, cite it. If you rely on a computational step, show the arithmetic. Later, as you and your reader gain experience, you can compress these steps. For now, err on the side of explicitness. A proof that is slightly too long but perfectly clear is better than one that is short but requires the reader to fill in crucial gaps.
Let us examine a simple proof in full, paying attention to structure. Theorem: The sum of two odd integers is even. Proof: Let a and b be odd integers. By definition, there exist integers k and m such that a = 2k + 1 and b = 2m + 1. Then a + b = (2k + 1) + (2m + 1) = 2k + 2m + 2 = 2(k + m + 1). Since k, m, and 1 are integers, k + m + 1 is an integer, so a + b is divisible by 2. Therefore, a + b is even. This proof starts by restating the definitions, performs algebraic manipulation that is justified by arithmetic laws, and concludes by connecting the result to the definition of even. It is short, but every step is justified.
As another example, consider proving that for any integer n, n^2 − n is even. A direct proof: n^2 − n = n(n − 1). The product of two consecutive integers always includes one even number because among any two consecutive integers, one is even. Therefore, n(n − 1) is even. Alternatively, you can prove by cases: if n is even, write n = 2k, then n^2 − n = 4k^2 − 2k = 2(2k^2 − k), which is even; if n is odd, write n = 2k + 1, then n^2 − n = (4k^2 + 4k + 1) − (2k + 1) = 4k^2 + 2k = 2(2k^2 + k), even again. Both approaches work. The case analysis is slightly longer but more mechanical. The first proof uses an observation about consecutive integers, which is elegant but requires you to notice or know the property. Both are valid and illustrate that multiple strategies may exist.
Proofs that rely on counting often appear in computer science. Suppose you have n distinct items and you want to show that a certain arrangement is possible. One approach is to construct the arrangement step by step, ensuring that you never paint yourself into a corner. Another is to show that the number of possible arrangements is positive, perhaps by deriving a formula and showing it is nonzero. For instance, the number of permutations of n items is n!, which is positive for all n ≥ 1, so at least one permutation exists. While trivial, this demonstrates how existence can be established via counting without producing an explicit permutation. If you need a constructive witness, you can give an algorithm that generates a permutation.
In computer science, you will often prove statements about programs. Even in informal settings, the principles of proof apply. You might say: “Assume the input array A has length n and contains integers. We claim that after running algorithm X, the array is sorted.” To justify this, you would identify a loop invariant, show it holds at initialization, and prove it is maintained by each iteration. Then you argue that when the loop ends, the invariant implies correctness. This is a proof by induction applied to the number of iterations, dressed up in code. The discipline of formal proof helps you find the right invariant and prevents you from relying on vague intuition.
Reading a proof is an active process. As you go through it, try to reconstruct it yourself. Cover the lines after each paragraph and attempt to write the next step. If you cannot, figure out what you are missing. Ask yourself: what is the role of each assumption? Could the proof work without a particular hypothesis? Would a weaker assumption suffice? This interrogation helps internalize the structure. It also helps you generalize the result. If you understand why the proof uses the parity of numbers, you might see how to adapt it to modular arithmetic in other bases.
Proofs can be presented in multiple formats: two-column proofs, paragraph proofs, or even graphical proofs. In mathematics, paragraphs are standard because they allow for exposition. In some contexts, like Euclidean geometry or logic, two-column proofs help beginners separate statements from justifications. In computer science, you will often see pseudocode or invariants stated as comments. The format matters less than the clarity of reasoning. Choose the format that best reveals the structure of your argument. A good proof makes its strategy visible at the top and then delivers the details with relentless consistency.
One useful habit is to summarize the proof in a sentence before writing it. For the even-sum proof, the summary might be: “Rewrite the odd numbers using their definitions, add them, and factor out 2.” This summary becomes the outline. If you cannot summarize, you probably do not yet understand the proof. For more complex proofs, outline the lemmas: what intermediate results are needed, and in what order? Prove lemmas first, then combine them. This modular approach reduces cognitive load and makes debugging easier. If the final result fails, you can test each lemma. If a lemma is wrong, you fix it there without overhauling the whole proof.
You will often encounter the phrase “without loss of generality” in proofs. This means that the argument you are about to give covers one of several cases, and the others follow by symmetry or a similar reasoning, so you do not need to repeat them. For instance, when proving a property of two numbers, you might assume that a ≤ b without loss of generality, because if a > b you can swap the labels. Be careful: the symmetry must be genuine. If the objects are distinguishable or the property is not symmetric, assuming a particular order may hide an essential difference. When in doubt, state the symmetry explicitly or handle all cases individually.
Another common phrase is “it suffices to show” or “we need only show.” This signals a reduction: the original claim is hard, but if we can establish a simpler intermediate statement, the original follows. Reduction is a core idea in computer science. To prove that an algorithm is optimal, you might reduce to a known lower bound. To prove a problem is hard, you reduce from a known hard problem. In proofs, reductions often simplify the argument. For instance, to show that a function is bijective, it suffices to show it is injective and surjective. Breaking a hard problem into smaller, well-defined subproblems is a universal strategy.
When writing proofs, precision with equality is vital. Be clear about when you are substituting equals for equals, when you are applying a function, and when you are using a property like associativity or distributivity. In computer science, you also need to be careful about whether you are working with values, references, or structural equivalence. In discrete math, the underlying equality is usually the mathematical one, but when modeling programs, you might have multiple equality notions. State your conventions. The most common error in algebraic proofs is performing an illegal step like dividing by an expression that could be zero. Always check special cases, or argue why they do not occur under your hypotheses.
Consider a small but instructive example: proving that for any integers a, b, c, if a divides b and b divides c, then a divides c. This is a direct proof. Since a divides b, there exists an integer k such that b = ak. Since b divides c, there exists an integer m such that c = bm. Substitute b from the first into the second: c = (ak)m = a(km). Since k and m are integers, km is an integer, so a divides c. This simple argument shows how to chain definitions and existence quantifiers. It also demonstrates the importance of substitution, a basic but powerful tool in proofs.
Another good exercise is to prove the “only if” direction of a characterization. For example, prove that a number is divisible by 6 if and only if it is divisible by 2 and by 3. The “if” part is easy: if divisible by 2 and 3, then by 6 because 2 and 3 are coprime. The “only if” part: if divisible by 6, write n = 6k, then n = 2(3k) so divisible by 2, and n = 3(2k) so divisible by 3. These proofs are straightforward but show the value of breaking a single condition into two. In software, similar decompositions occur when a precondition is a conjunction of conditions, each of which must be verified independently.
Proofs by induction can be over natural numbers, over structures like lists or trees, or over more complex well-founded sets. The principle is always the same: show that the property holds for minimal elements, and show that assuming it holds for “smaller” elements, it also holds for a larger element. For natural numbers, the base case is n = 0 or n = 1. The inductive step assumes P(n) and proves P(n+1). In computer science, you will often use structural induction on data types. For example, to prove that a property holds for all binary trees, prove it for the empty tree, and prove that if it holds for the left and right subtrees, it holds for the tree composed of them. Induction then guarantees the property for all trees.
Invariants and induction are closely related. In a loop, an invariant is a statement that remains true before and after each iteration. To prove that the invariant holds, you often reason by induction on the number of iterations. For example, consider a loop that sums the elements of an array. The invariant might be “sum_so_far equals the sum of the first k elements processed.” Initialization establishes it for k = 0; each iteration extends k by 1 and updates sum_so_far accordingly, preserving the invariant. When the loop ends, k = n, and the invariant implies correctness. This style of reasoning is the bridge between mathematics and imperative programming. It is why algorithm correctness proofs feel like induction dressed in code.
Mathematical writing benefits from a few conventions. Variables introduced by “let” or “take” are assumed to satisfy the hypotheses. Words like “some” and “any” carry weight: “some x” means there exists at least one; “any x” typically means for all x. Be wary of starting a sentence with “any” unless the context is clear. Avoid ambiguous phrases like “it is easy to see.” If it is easy, show it briefly; if it is not, explain why. When using symbols, define them. A proof that begins with “Let G = (V, E) be a graph” is clear because it tells the reader what G, V, and E represent. These small habits create trust.
A proof is not complete until it has been checked for hidden assumptions and edge cases. If your proof relies on a property of integers that fails for negative numbers, your claim may be false or your scope too broad. If you assume that a graph is connected, but the theorem is supposed to hold for all graphs, your proof fails. Always ask: what happens at the boundary? At zero, at one, at empty, at negative? For example, a function defined by a formula may have a domain restriction that excludes certain inputs. Theorems should state the domain explicitly. When proving a theorem about natural numbers, say so; do not rely on the reader to infer it.
As you build experience, you will notice that proofs often cluster into families. The family of “algebraic manipulation” proofs includes many identities in combinatorics and number theory. The family of “graph traversal” proofs uses reachability arguments. The family of “counting” proofs uses double counting or the pigeonhole principle. The family of “adversarial” proofs uses extremal choices. Recognizing the family helps you anticipate the right technique. When you see a claim about existence, ask if a construction is natural or if a counting argument is easier. When you see a claim about impossibility, try contradiction or an adversary argument.
Proofs also have a social dimension: they are written for others to verify. A proof that convinces you may not convince a reader if your reasoning is implicit. Therefore, a good proof anticipates questions: Why does this step follow? What if the denominator is zero? Does this property hold for all integers, or just for primes? Address these questions proactively. A proof is not a monologue; it is a carefully structured explanation. The best proofs are the ones that teach the reader something, not just about the theorem, but about the strategy that uncovered it.
Finally, remember that learning to read and write proofs is a skill that grows with practice. Start with small, self-contained claims and build up to longer, multi-part arguments. Keep a notebook of interesting proof strategies you encounter. When you get stuck, step back and restate the problem in your own words. Try to explain it to a friend or even to yourself out loud. Often, the act of articulating the claim reveals the missing link. Over time, the patterns will become familiar, the definitions will become second nature, and the structure of a proof will feel like the structure of a well-written program: clear, modular, and correct.
This is a sample preview. The complete book contains 27 sections.