Chain-of-thought prompting improves math accuracy by forcing the model to decompose problems into intermediate steps rather than jumping to answers. This mirrors how humans solve complex arithmetic: we don’t intuit 47 × 23, we break it into partial products. The technique shifts computation from opaque pattern matching to explicit reasoning, and the difference shows up clearly in evaluation benchmarks across model sizes.
Why standard prompting fails at math
Standard few-shot prompting treats math as a classification task. The model sees “What is 47 × 23?” and predicts the most likely token sequence following similar patterns in its training data. For simple arithmetic this works — multiplication tables are well-represented. But as soon as problems require multi-step reasoning, the probability of the correct final token plummets because the model never learned to “carry the one” as a distinct operation.
Consider this zero-shot prompt:
prompt = "What is 47 * 23?"
A 7B parameter model typically answers 1081 (correct) or 1082 (off by one). But ask it to solve (47 * 23) + (15 * 32) - 100 and accuracy collapses. The model tries to predict the final number directly, hallucinating intermediate values that never get verified.
The failure mode is structural: next-token prediction has no built-in mechanism for self-correction. Once the first digit of the answer is wrong, every subsequent token conditions on that error.
How chain-of-thought changes the computation
Chain-of-thought prompting appends “Let’s think step by step” or provides explicit reasoning examples. This does two things mechanically:
- Expands the context window with intermediate tokens that serve as a scratchpad
- Conditions each step on the previous step’s output, creating a verification chain
cot_prompt = """Q: What is (47 * 23) + (15 * 32) - 100?
A: Let's think step by step.
First, calculate 47 * 23:
47 * 20 = 940
47 * 3 = 141
940 + 141 = 1081
Next, calculate 15 * 32:
15 * 30 = 450
15 * 2 = 30
450 + 30 = 480
Now add them: 1081 + 480 = 1561
Finally subtract 100: 1561 - 100 = 1461
The answer is 1461."""
The model now generates 1461 after producing verifiable intermediate values. Each arithmetic operation becomes a separate prediction conditioned on correct predecessors. Errors still occur, but they’re localized — a mistake in 47 × 23 doesn’t cascade silently into the final answer because the addition step conditions on the written intermediate result.
Concrete comparison on GSM8K-style problems
The GSM8K dataset (grade-school math word problems) is the standard benchmark for chain-of-thought math accuracy. Here’s what the difference looks like in practice:
import json
problems = [
"Janet has 24 apples. She gives 1/3 to her brother and 1/4 to her sister. How many apples does she have left?",
"A train travels 60 mph for 2.5 hours, then 70 mph for 1.5 hours. What is the total distance?",
"If 3 shirts cost $45, how much do 7 shirts cost?"
]
standard_prompt = "Q: {}\nA:"
cot_prompt = "Q: {}\nA: Let's think step by step.\n"
# Standard prompting typically yields:
# "A: 12" (wrong - no reasoning shown)
# "A: 225 miles" (correct but unverifiable)
# "A: $105" (correct but brittle)
# Chain-of-thought yields:
# "A: Let's think step by step.
# Janet starts with 24 apples.
# 1/3 to brother: 24 * 1/3 = 8 apples.
# 1/4 to sister: 24 * 1/4 = 6 apples.
# Total given away: 8 + 6 = 14 apples.
# Remaining: 24 - 14 = 10 apples.
# The answer is 10."
On a 7B model, standard prompting achieves roughly 15-25% accuracy on GSM8K. Chain-of-thought pushes this to 45-55%. On 70B+ models, the gap narrows but persists: 70% vs 85%. The improvement comes from making the model do the work in the forward pass rather than compressing reasoning into a single token prediction.
The mechanism: why intermediate tokens help
Transformers compute attention over all previous tokens. When you force the model to write “47 * 20 = 940”, that equation becomes part of the context for predicting “940 + 141 = 1081”. The attention mechanism can now attend to the explicit multiplication rather than reconstructing it from weights.
This is computationally distinct from internal reasoning. The model’s weights could encode multiplication algorithms, but gradient descent optimizes for next-token prediction on internet text — which rarely shows step-by-step arithmetic. Chain-of-thought moves the algorithm from weights (where it’s poorly learned) to activations (where it’s explicitly executed).
# What the model effectively does with CoT:
# Step 1: Attend to "47 * 20" → predict "940"
# Step 2: Attend to "47 * 3" → predict "141"
# Step 3: Attend to "940 + 141" → predict "1081"
# Each step is a simple pattern the model saw during training
# Without CoT:
# Step 1: Attend to "47 * 23" → predict "1081" (memorized) or hallucinate
The model isn’t “thinking” — it’s executing a program written in natural language, where each line is a simple next-token prediction it handles reliably.
Tradeoffs you need to weigh
Latency and cost
Chain-of-thought increases output tokens 3-10x. On a typical GSM8K problem, standard prompting generates ~5 tokens. CoT generates 50-200. At $0.50-2.00 per million output tokens (depending on provider), this adds up fast at scale.
# Rough token economics for 1M math problems:
# Standard: ~5M output tokens → $2.50-10
# CoT: ~100M output tokens → $50-200
If you’re running a high-volume API, this matters. Some teams use a two-stage approach: cheap model generates CoT, expensive model verifies the final answer only.
Verbosity control
Unconstrained CoT produces inconsistent formatting. Some models write paragraphs, others bullet points. This breaks downstream parsing.
# Better: constrain the format
cot_prompt_structured = """Q: {}
A: Let's solve this step by step.
Step 1: [operation]
Result: [value]
Step 2: [operation]
Result: [value]
Final answer: [number]"""
Structured CoT adds ~10% more tokens but makes extraction reliable. Use regex or a small parser rather than another LLM call.
When CoT hurts
Chain-of-thought can reduce accuracy on:
- Simple retrieval: “What is 7 * 8?” — CoT adds noise
- Pattern matching: “Complete the sequence: 2, 4, 8, 16, ?” — explicit reasoning may override correct intuition
- Adversarial prompts: CoT gives attackers more surface area for injection
Test your specific problem distribution. Don’t assume CoT helps universally.
Alternatives and complements
Program-aided language (PAL)
Instead of natural language reasoning, have the model write Python:
pal_prompt = """Q: Janet has 24 apples. She gives 1/3 to her brother and 1/4 to her sister. How many apples does she have left?
A: Let's write a Python program to solve this.
total = 24
brother = total * 1/3
sister = total * 1/4
remaining = total - brother - sister
print(remaining)"""
PAL offloads arithmetic to a Python interpreter — zero hallucination on calculation steps. The model only needs to translate the problem correctly. Accuracy on GSM8K jumps another 10-15% over CoT. The tradeoff: you need a code execution environment, and the model must generate valid syntax.
Self-consistency
Run CoT multiple times (temperature > 0) and take the majority answer:
import statistics
def self_consistency(prompt, n=5, temperature=0.7):
answers = []
for _ in range(n):
response = model.generate(prompt, temperature=temperature)
answer = extract_final_number(response)
answers.append(answer)
return statistics.mode(answers)
This costs 5x inference but can push 7B models from 55% to 70%+ on GSM8K. Worth it when accuracy is critical and latency isn’t.
Fine-tuning on CoT traces
If you control the model, fine-tune on high-quality CoT examples. This bakes the reasoning pattern into weights, eliminating the token overhead at inference. The resulting model solves math problems in fewer tokens because it “internalized” the steps. This is how models like DeepSeek-Math and WizardMath achieve strong results without explicit CoT prompting.
Practical implementation pattern
Here’s a production-ready pattern for math-heavy workloads:
import re
from typing import Optional
class MathSolver:
def __init__(self, client, model: str):
self.client = client
self.model = model
def solve(self, problem: str, use_cot: bool = True) -> dict:
if use_cot:
prompt = self._cot_prompt(problem)
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=0.0, # Deterministic for math
max_tokens=500
)
reasoning = response.choices[0].message.content
answer = self._extract_answer(reasoning)
return {"answer": answer, "reasoning": reasoning, "method": "cot"}
else:
prompt = f"Q: {problem}\nA:"
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
max_tokens=20
)
answer = self._extract_answer(response.choices[0].message.content)
return {"answer": answer, "reasoning": None, "method": "direct"}
def _cot_prompt(self, problem: str) -> str:
return f"""Q: {problem}
A: Let's solve this step by step.
Step 1: Identify what we know and what we need to find.
Step 2: Perform each calculation explicitly.
Step 3: State the final answer clearly.
"""
def _extract_answer(self, text: str) -> Optional[float]:
# Match "The answer is X" or "Final answer: X" or just trailing number
patterns = [
r"(?:answer|result)\s*(?:is|=|:)\s*([\d,]+\.?\d*)",
r"([\d,]+\.?\d*)\s*$"
]
for pattern in patterns:
match = re.search(pattern, text, re.IGNORECASE)
if match:
return float(match.group(1).replace(",", ""))
return None
Key decisions in this pattern:
- Temperature 0 — math should be deterministic
- Explicit extraction — don’t rely on the model formatting perfectly
- Method tracking — log which approach you used for evaluation
- Max tokens capped — prevents runaway CoT on pathological inputs
Evaluation: measure what matters
Don’t trust benchmark numbers. Evaluate on your problem distribution:
def evaluate_solver(solver, test_cases: list[dict], use_cot: bool):
correct = 0
total = 0
for case in test_cases:
result = solver.solve(case["problem"], use_cot=use_cot)
if abs(result["answer"] - case["answer"]) < 0.001:
correct += 1
total += 1
return correct / total
# Your test cases should reflect real usage:
test_cases = [
{"problem": "Calculate 15% tip on $87.50", "answer": 13.125},
{"problem": "If 5 servers handle 200 req/s, how many for 500 req/s?", "answer": 12.5},
{"problem": "Compound interest: $1000 at 5% for 3 years", "answer": 1157.625},
]
Track accuracy and latency and token cost. A 5% accuracy gain that doubles p99 latency may not be worth it for your API.
The decisive takeaway
Chain-of-thought prompting improves math accuracy because it converts implicit reasoning into explicit tokens that the transformer can attend to and verify. It’s not magic — it’s moving computation from poorly-learned weights into the forward pass where each step is a simple, high-probability prediction.
Use it when:
- Problems require 3+ reasoning steps
- You can tolerate 3-10x output tokens
- You implement structured parsing and deterministic sampling
Skip it when:
- Problems are single-step arithmetic or retrieval
- Latency budget is tight and accuracy is already acceptable
- You can use PAL with a code executor instead (better accuracy, similar cost)
The engineers who get the most mileage from CoT treat it as a programming technique — they write the reasoning program in the prompt, constrain the output format, and measure rigorously on their actual workload. The rest treat it as a magic phrase and wonder why their p99 latency spiked.