Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/leanprover/lean4/llms.txt

Use this file to discover all available pages before exploring further.

Tactic mode is the interactive, goal-directed style of proof in Lean 4. You enter it with the by keyword, and from that point you issue a sequence of tactics that progressively transform proof goals until none remain. Each tactic consumes one or more goals and may produce new subgoals.

The Tactic State

When working in an IDE with the Lean 4 language server, you see the tactic state at the cursor position:
a b : Nat
h : a < b
⊢ a ≤ b
The section above contains your local hypotheses (variables and assumptions). The line after is the current goal — the type you must inhabit to complete the proof.
Multiple goals are displayed stacked. Use · (focused dot) or case labels to work on a specific goal. The <;> combinator applies a tactic to all goals produced by the previous tactic.

Introducing Hypotheses

intro / intros

intro moves universally-quantified variables and implication antecedents from the goal into the context. intros (plural) does the same repeatedly until the goal is no longer a binder.
theorem forall_imp (p q : α → Prop) : (∀ x, p x) → (∀ x, p x → q x) → ∀ x, q x := by
  intro hall hpq x
  exact hpq x (hall x)

-- intros introduces all binders at once, giving inaccessible names
example (p q : Prop) : p → q → p := by
  intros
  assumption
intro also supports inline destructuring patterns when introducing a pair or product type from the goal:
-- Introducing a pair: splits it into components
example : ∀ p : Nat × Nat, p.1 + p.2 = p.2 + p.1 := by
  intro (a, b)   -- destructs the pair into a and b
  omega

Closing Goals

exact

exact e closes the goal when e has exactly the required type.
theorem exact_example (p : Prop) (hp : p) : p := by
  exact hp

assumption

assumption searches the local context for a hypothesis that matches the goal.
theorem assumption_example (p q : Prop) (hp : p) (hq : q) : q := by
  assumption

trivial

trivial tries several cheap tactics in sequence: rfl, trivial (recursively), assumption, and decide. It closes simple goals like ⊢ True.
example : True := by trivial
example : 12 := by trivial

contradiction

contradiction closes the goal when the hypotheses contain an obvious contradiction — for example h₁ : p and h₂ : ¬p, or h : False, or a hypothesis of an uninhabited inductive type.
theorem contradiction_example (p : Prop) (hp : p) (hnp : ¬p) : False := by
  contradiction

theorem absurd_example (h : 0 = 1) : False := by
  contradiction

Applying Lemmas

apply

apply f unifies the conclusion of f with the current goal and creates new goals for each unresolved premise of f.
theorem apply_example (p q r : Prop) (h₁ : p → q) (h₂ : q → r) (hp : p) : r := by
  apply h₂
  apply h₁
  exact hp

refine

refine e is like exact, but allows ?_ placeholders that become new goals.
theorem refine_example (p q : Prop) (hp : p) (hq : q) : p ∧ q := by
  refine ⟨?_, ?_⟩
  · exact hp
  · exact hq

Rewriting

rw

rw [h] rewrites the goal using the equation h : a = b (left-to-right by default). Use ←h to rewrite right-to-left. You can provide a list: rw [h₁, h₂, ...].
theorem rw_example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by
  rw [h]

theorem rw_symm_example (a b : Nat) (h : a = b) : b + 1 = a + 1 := by
  rw [← h]
rw [h] at hyp rewrites in a hypothesis instead of the goal.

simp

simp applies a curated set of lemmas tagged @[simp] repeatedly until no further simplifications apply. It subsumes many common proof steps.
theorem simp_example (n : Nat) : n + 0 = n := by simp
theorem simp_list : [1, 2, 3].length = 3 := by simp
simp [h₁, h₂] adds extra lemmas to the simp set. simp only [h] uses only the given lemmas (no default simp set). simp at h simplifies a hypothesis.
-- simp only is precise and faster for large goals
theorem simp_only_example (a b : Nat) (h : a = b) : a + 0 = b := by
  simp only [Nat.add_zero, h]
Prefer simp only in production proofs to avoid brittle dependencies on the global simp set. Use bare simp for quick exploration.

Arithmetic Tactics

omega

omega is a complete decision procedure for linear arithmetic over Nat and Int. It handles equalities, inequalities, divisibility by literals, and natural-number subtraction.
theorem omega_nat (n m : Nat) (h : n + m = 10) : m ≤ 10 := by omega

theorem omega_int (x y : Int) (h₁ : x > 0) (h₂ : y > 0) : x + y > 1 := by omega

theorem omega_mod (n : Nat) : n % 2 = 0 ∨ n % 2 = 1 := by omega

grind

grind is a powerful general-purpose automation tactic that combines congruence closure, equational reasoning, E-matching, case splitting, and multiple specialized solvers (including linear arithmetic and ring arithmetic). It is especially effective at goals involving many equalities, logical connectives, and arithmetic.
theorem grind_example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by grind

theorem grind_logic (p q : Prop) (hp : p) (hq : q) : p ∧ q := by grind

theorem grind_arith (n m : Nat) (h₁ : n ≤ m) (h₂ : m ≤ n) : n = m := by grind
grind subsumes many common arithmetic and logical goals. Use grind? to have Lean report the minimal grind only [...] invocation needed.
grind is a built-in Lean 4 tactic. The Mathlib library provides additional specialized tactics such as ring (ring equalities), linarith (linear arithmetic over ordered fields), and norm_num (concrete numeric computations). These are not part of the Lean 4 core and require importing Mathlib.

lia

lia solves linear integer arithmetic goals. It is a thin wrapper around grind that enables only the linear arithmetic solver.
theorem lia_example (x y : Int) (h₁ : x ≥ 0) (h₂ : y ≥ 0) (h₃ : x + y ≤ 5) :
    x ≤ 5 := by lia

theorem lia_nat (n m : Nat) (h : n + m = 10) : m ≤ 10 := by lia
For most linear arithmetic goals over Nat and Int, omega is preferred since it is complete and handles natural-number subtraction. Use lia when working with integers that have signed semantics.

decide

decide proves goals of a Decidable proposition by reducing them via the kernel. It is complete for any proposition with a Decidable instance.
theorem decide_example : 2 + 2 = 4 := by decide
theorem decide_ne : 12 := by decide
theorem decide_list : [1, 2, 3].contains 2 = true := by decide
decide reduces terms in the kernel, which can be slow for large computations. Use native_decide for faster evaluation at the cost of trusting the native compiler.

Case Analysis and Induction

cases

cases h performs case analysis on an inductive value h, producing one goal per constructor.
theorem cases_example (p q : Prop) (h : p ∨ q) : q ∨ p := by
  cases h with
  | inl hp => right; exact hp
  | inr hq => left; exact hq

theorem cases_nat (n : Nat) : n = 0 ∨ ∃ m, n = m + 1 := by
  cases n with
  | zero => left; rfl
  | succ m => right; exact ⟨m, rfl⟩

rcases

rcases h with pattern is a powerful recursive pattern-matching tactic that can destructure nested And, Or, Exists, and constructor types in one step.
theorem rcases_and (h : p ∧ q ∧ r) : r := by
  rcases h with ⟨_, _, hr⟩
  exact hr

theorem rcases_or (h : p ∨ q ∨ r) : r ∨ p ∨ q := by
  rcases h with hp | hq | hr
  · exact Or.inr (Or.inl hp)
  · exact Or.inr (Or.inr hq)
  · exact Or.inl hr

obtain

obtain is like rcases but uses have-like syntax. It is especially clean for goals:
theorem obtain_exists (h : ∃ n : Nat, n > 5) : ∃ n : Nat, n > 0 := by
  obtain ⟨n, hn⟩ := h
  exact ⟨n, by omega⟩

induction

induction x applies structural induction on x, producing one goal per constructor. The induction hypothesis is named ih by default.
theorem induction_example (n : Nat) : 0 + n = n := by
  induction n with
  | zero      => rfl
  | succ n ih => simp [Nat.add_succ, ih]

-- induction with explicit induction principle
theorem induction_on_list (l : List α) : l.length + 0 = l.length := by
  induction l with
  | nil        => rfl
  | cons x xs ih => simp [List.length_cons, ih]

Building Terms

constructor

constructor applies the first applicable constructor of the goal type.
theorem constructor_and (hp : p) (hq : q) : p ∧ q := by
  constructor
  · exact hp
  · exact hq

left / right

left applies Or.inl; right applies Or.inr.
theorem left_example (hp : p) : p ∨ q := by left; exact hp

use (via exists)

exists e (built on refine ⟨e, ?_⟩) provides a witness for an existential goal:
theorem use_example : ∃ n : Nat, n * n = 9 := by
  exact ⟨3, by decide⟩

-- The `exists` tactic macro:
theorem exists_tac : ∃ n : Nat, n > 100 := by
  exists 101

Structuring Proofs

have

have h : t := e or have h : t := by tac introduces a new local hypothesis.
theorem have_example (n : Nat) (h : n > 5) : n > 3 := by
  have h' : n > 4 := by omega
  omega

suffices

suffices h : t from e replaces the goal with t, using e to show the original goal follows from t.
theorem suffices_example (n : Nat) : n + 2 > n := by
  suffices h : n + 1 > n by omega
  omega

show

show t changes the displayed form of the goal to t (which must be definitionally equal to the current goal).
theorem show_example (n : Nat) : n + 0 = n := by
  show n = n  -- 0-add normalizes
  rfl

Congruence and Focusing

conv

conv enters a sub-tactic mode for navigating inside a term to perform targeted rewrites.
theorem conv_example (a b : Nat) : a + b = b + a := by
  conv_lhs => rw [Nat.add_comm]

-- Rewrite only the left-hand side of an inner addition
theorem conv_inner (a b c : Nat) : (a + b) + c = (b + a) + c := by
  conv_lhs => rw [Nat.add_comm a b]

Tactic Combinators

Tactic combinators let you sequence, branch, and repeat tactics.
tac₁ <;> tac₂ runs tac₁ and then applies tac₂ to every goal produced.
theorem semicolon_example (p q : Prop) (hp : p) (hq : q) : p ∧ q := by
  constructor <;> assumption

Quick Reference Table

TacticPurpose
intro xIntroduce a hypothesis or variable
exact eClose goal with term e
assumptionClose goal with a matching hypothesis
apply fApply lemma f, creating premise goals
refine eLike exact with ?_ holes
rw [h]Rewrite with equation h
simpSimplify using @[simp] lemmas
simp only [h]Simplify using only h
omegaLinear arithmetic over Nat/Int
liaLinear integer arithmetic (grind wrapper)
grindGeneral-purpose: congruence, arithmetic, logic
decideDecidable propositions by reduction
cases hCase-split on inductive value
rcases h with patRecursive pattern case-split
obtain ⟨x, hx⟩ := hDestructure or
induction xStructural induction
constructorApply first constructor
left / rightApply Or.inl / Or.inr
have h : t := ...Introduce local hypothesis
suffices h : t by ...Reduce to a sufficient condition
show tRename goal (definitional equality)
conv => ...Targeted term navigation
trivialClose trivial goals
contradictionClose contradictory contexts
<;>Apply tactic to all goals
first | t₁ | t₂Ordered choice
try tOptional tactic
repeat tRepeat until failure

Build docs developers (and LLMs) love