Proof Workflow
Learn a repeatable process for building proofs: read the goal, introduce assumptions, simplify, and close with a finishing tactic.
Read the Goal First
Every proof starts with a goal. Before choosing a tactic, identify the shape of the goal and what it expects. The goal appears after the ⊢symbol in the Infoview. Understanding the goal's structure—whether it's an implication, a universal statement, an equality, or a conjunction—determines which tactic to use.
Common goal shapes and their meanings:
P → Q: An implication—assume P, prove Q∀ x, P x: A universal—prove P for an arbitrary xa = b: An equality—show both sides are the sameP ∧ Q: A conjunction—prove both P and QP ∨ Q: A disjunction—prove at least one of P or Q
1example : Nat → Nat := by2 -- Goal: Nat → Nat3 -- This is a function type, so we introduce the input4 intro n5 -- Goal: Nat6 -- Now we just need to produce a Nat7 exact n + 1The Infoview updates after each tactic, showing how the goal changes. Watch it like a debugging panel—it tells you exactly what remains to prove.
Introduce, Then Simplify
Most goals become easier once you move assumptions into context and simplify the target. The general strategy is: first use intro to bring universals and implications into your hypothesis list, then use simp orrw to clean up the goal.
This "introduce then simplify" pattern handles the majority of proofs:
1-- Simple simplification2example (n : Nat) : n + 0 = n := by3 simp -- simp knows n + 0 = n45-- Reflexivity when both sides are identical6example (n : Nat) : n = n := by7 rfl -- Both sides compute to the same thingWhen you have hypotheses available, include them in your simplification:
1example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by2 simp [h] -- Uses h to rewrite a to b, then simplifies34example (n : Nat) (h : n = 5) : n + 0 = 5 := by5 simp only [Nat.add_zero, h] -- Explicit about which lemmasClose with a Finisher
If the goal is exactly a hypothesis, use exact orassumption. If it’s a definitional equality, try rfl. These "finisher" tactics close the proof when only the final step remains.
exact requires you to specify exactly which term proves the goal.assumption searches your hypotheses automatically. Useexact when you know what you need; use assumptionwhen the answer is obviously in scope.
1-- exact: specify the proof term2example (P Q : Prop) (hp : P) : P := by3 exact hp45-- assumption: auto-search hypotheses6example (P Q : Prop) (hp : P) (hq : Q) : P := by7 assumption -- Finds hp automatically89-- rfl: definitional equality10example : (2 : Nat) = 2 := by11 rfl1213-- trivial: handles True and similar goals14example : True := by15 trivialIf none of these work, your goal likely needs further transformation. Go back to simplification or try rewriting with rw.
Pick the Right Tactic
A fast workflow is about choosing the simplest tactic that moves the goal forward. Here's a quick reference for common goal shapes:
| Goal Shape | Primary Tactic | What It Does |
|---|---|---|
| P → Q | intro | Assume P, prove Q |
| ∀ x, P x | intro | Let x be arbitrary |
| P ∧ Q | constructor | Split into two goals |
| P ∨ Q | left or right | Pick which to prove |
| a = b | rfl, rw, simp | Make sides equal |
| ∃ x, P x | exists (core), use (Mathlib) | Provide a witness |
When in doubt, try simp first. It often cleans up the goal enough that the next step becomes obvious.
simp early. It often turns a complicated goal into somethingrfl can close.Debugging Stuck Proofs
Even experienced users get stuck. Here's how to diagnose and fix common issues:
Check the Goal Carefully
Sometimes the goal looks right but has subtle differences—like implicit arguments or different universes. Hover over terms in the Infoview to see their full types.
Use simp? and exact?
These "?" tactics suggest what to use:
1example (n : Nat) : n + 0 = n := by2 simp?3 -- Try this:4 -- [apply] simp only [Nat.add_zero]56example (P : Prop) (hp : P) : P := by7 exact? -- Try this: exact hpsimp with the simp only [...] that simp?reports, at least in code you intend to keep. A bare simp depends on the entire simp set, which changes whenever you add an import or upgrade a library — proofs that used to work silently break. simp only names exactly what it needs and keeps working.Prove the Goal Backwards
Do not be afraid to write a proof skeleton full of sorryand fill it in afterwards. Lean will typecheck the structure and tell you the exact goal at each hole — which is often the information you were missing.
1example (a b : Nat) (h : a ≤ b) : a * 2 ≤ b * 2 := by2 have step1 : a + a ≤ b + b := by sorry -- fill in later3 have step2 : a * 2 = a + a := by sorry4 sorry5-- Each sorry shows you a concrete, smaller goal in the Infoview.6-- Replace them one at a time; the structure is already checked.Break Complex Goals Apart
If the goal is overwhelming, use have to prove intermediate steps:
1example (xs ys : List Nat) (h : xs = ys) : xs.length + 1 = ys.length + 1 := by2 -- Prove the small fact first, give it a name...3 have hlen : xs.length = ys.length := by rw [h]4 -- ...then the main goal follows from it5 rw [hlen]67-- have is also how you introduce a fact the goal does not mention:8example (n : Nat) (h : n > 3) : n * 2 > 6 := by9 have h4 : n ≥ 4 := h10 omegaUse intro and exact to prove the following:
1example (P Q : Prop) : P → Q → P := by2 intro hp hq3 exact hpUse constructor to prove a conjunction.
1example (P Q : Prop) (hp : P) (hq : Q) : P ∧ Q := by2 constructor3 · exact hp4 · exact hq