Skip to content

Mistake Journal

A living document for recording, categorizing, and learning from your contest mistakes -- because the same bugs cost you rating over and over until you name them.

Prerequisites: Debugging Under Pressure, Reflections, and at least 15 rated contests with WA/TLE verdicts you still remember.

Quick Revisit

  • Replay-style file: structured mistake tracking, pattern extraction, and specific fixes by technique.
  • Core observation: your rating is limited by the bugs you keep re-introducing, not the algorithms you do not know.
  • The entry template prescribes the schema; the sample entries show the level of specificity that makes the journal useful instead of cathartic.

Contents


Why This Exists

You will make the same mistake three times. The first time, you'll blame the problem. The second time, you'll blame bad luck. The third time -- if you're paying attention -- you'll notice the pattern.

Most people never notice. They hit WA, upsolve, move on, and walk straight into the same wall next Saturday. The mistake journal is the thing that breaks the cycle.

Here's the uncomfortable truth: your rating is not limited by the algorithms you don't know. It's limited by the bugs you keep re-introducing. A 1600-rated coder who eliminates their top 5 recurring mistakes is a 1800 coder. No new algorithms required.

The evidence is in your own contest history. Go look at your last 10 wrong submissions on Codeforces. At least 4 of them fall into the same 2-3 categories. You already know which ones. You just haven't written them down.

Writing them down is the whole point.


The Workflow

 Before every contest:

 +---------------------------------------------+
 | 1. Open this file |
 | 2. Read your last 10 entries |
 | 3. For each: say the root cause out loud |
 | 4. Note which categories appear most often |
 | 5. During the contest, when you're about |
 | to submit -- pause and ask: |
 | "Is this one of my usual mistakes?" |
 +---------------------------------------------+

That's 5 minutes. It'll save you 30.

After every contest (within 24 hours, while the pain is fresh):

  1. For every WA/TLE/RE -- add an entry below.
  2. Be honest about the root cause. "I don't know" is not a root cause. Dig deeper.
  3. Link the entry to the relevant technique file in this notebook.
  4. If you see the same root cause 3+ times, that's a focused practice session, not a "be more careful" moment.

Entry Template

Copy this table for each mistake. Fill it out while the contest is still fresh.

The template follows the replay arc: what you tried (first instinct), what went wrong (the wrong path), root cause analysis (the reframing), and what to do next time (the transfer lesson).

+------------------+--------------------------------------------------------+
| Date | YYYY-MM-DD |
+------------------+--------------------------------------------------------+
| Problem | [CF 1923D / Div2 C](https://codeforces.com/...) |
+------------------+--------------------------------------------------------+
| What I tried | Binary search on answer + prefix sums |
+------------------+--------------------------------------------------------+
| What went wrong | WA on test 4 -- binary search returned r instead of l, |
| | giving off-by-one on the boundary |
+------------------+--------------------------------------------------------+
| Root cause | Wrong technique implementation (Off-by-one) |
+------------------+--------------------------------------------------------+
| What I should | Always write out the invariant before coding the |
| have done | binary search: "l is always invalid, r is always |
| | valid" -- then the return value is unambiguous |
+------------------+--------------------------------------------------------+
| Time lost | 45 min + 2 penalties |
+------------------+--------------------------------------------------------+
| Topic to review | 02-binary-search.md |
+------------------+--------------------------------------------------------+

Sample Entries

Three filled entries showing the level of specificity to aim for. Notice that each one names a concrete behavioral fix, not just "be more careful."

+------------------+--------------------------------------------------------+
| Date             | 2024-03-09                                             |
+------------------+--------------------------------------------------------+
| Problem          | Round 932 / Div2 D — minimum k such that f(k) >= T     |
+------------------+--------------------------------------------------------+
| What I tried     | Binary search on k in [1, 1e9]; check via prefix sum.  |
+------------------+--------------------------------------------------------+
| What went wrong  | WA on test 4. After the loop, I returned `r` but my    |
|                  | invariant was "l = last invalid, r = first valid", so  |
|                  | the answer should have been `l + 1` or directly `r`    |
|                  | with hi initialized one past the max — I had hi = 1e9, |
|                  | so r could equal hi when no valid value exists.        |
+------------------+--------------------------------------------------------+
| Root cause       | (7) Off-by-one — initialized hi too tight, did not     |
|                  | distinguish "no valid k" from "k = 1e9".               |
+------------------+--------------------------------------------------------+
| What I should    | Always set hi = (max possible answer) + 1, and treat   |
| have done        | "answer == hi" as the sentinel for "no valid value".   |
|                  | Write the invariant as a comment ABOVE the loop.       |
+------------------+--------------------------------------------------------+
| Time lost        | 28 min + 1 penalty                                     |
+------------------+--------------------------------------------------------+
| Topic to review  | 01-essential-techniques/04-binary-search.md            |
+------------------+--------------------------------------------------------+

Sample 2 — Forgot multi-test reset

+------------------+--------------------------------------------------------+
| Date             | 2024-04-14                                             |
+------------------+--------------------------------------------------------+
| Problem          | Round 940 / Div2 C — graph queries, t up to 1e4.       |
+------------------+--------------------------------------------------------+
| What I tried     | DSU + edge list. Declared `dsu` and `edges` as global. |
+------------------+--------------------------------------------------------+
| What went wrong  | AC on test 1, WA on test 2 — DSU parents from case 1   |
|                  | leaked into case 2.                                    |
+------------------+--------------------------------------------------------+
| Root cause       | (2) Right technique, wrong implementation — global     |
|                  | state across test cases.                               |
+------------------+--------------------------------------------------------+
| What I should    | Declare per-case state INSIDE solve(). When that is    |
| have done        | not feasible (large arrays), add a `// RESET HERE`     |
|                  | comment at the top of solve() and explicitly clear     |
|                  | every container.                                       |
+------------------+--------------------------------------------------------+
| Time lost        | 18 min + 1 penalty                                     |
+------------------+--------------------------------------------------------+
| Topic to review  | 11-contest-craft/03-common-templates.md                |
|                  | (Multi-Test-Case Reset Checklist)                      |
+------------------+--------------------------------------------------------+

Sample 3 — Greedy without proof

+------------------+--------------------------------------------------------+
| Date             | 2024-05-02                                             |
+------------------+--------------------------------------------------------+
| Problem          | Round 945 / Div2 C — minimize total cost.              |
+------------------+--------------------------------------------------------+
| What I tried     | Greedy: always pick the cheapest available option.     |
|                  | Sorted by cost, walked through samples — passed all 4. |
+------------------+--------------------------------------------------------+
| What went wrong  | WA on test 6. Counterexample (after upsolving): a      |
|                  | locally expensive choice unlocked two cheap follow-ups |
|                  | that the greedy never considered.                      |
+------------------+--------------------------------------------------------+
| Root cause       | (1) Wrong technique — greedy choice property fails;    |
|                  | the problem was actually DP on cost states.            |
+------------------+--------------------------------------------------------+
| What I should    | Before submitting any greedy, write the exchange       |
| have done        | argument as a one-line comment. If I cannot produce    |
|                  | the swap step in 60 seconds, the greedy is suspect —   |
|                  | go back to DP.                                         |
+------------------+--------------------------------------------------------+
| Time lost        | 35 min + 2 penalties + lost the problem entirely       |
+------------------+--------------------------------------------------------+
| Topic to review  | 11-contest-craft/07-dp-vs-greedy-guide.md              |
+------------------+--------------------------------------------------------+

The pattern across the three samples: root cause is a category, not a story; the "what I should have done" is a behavioral rule that future-you can execute. "Be more careful" is not an entry. "Set hi = max+1 and use answer == hi as the no-solution sentinel" is.


Root Cause Categories

Every mistake you make in a contest falls into one of these 8 buckets. When you fill in an entry above, pick one. If two apply, pick the one that happened first -- that's the one that would have prevented the cascade.

 Category What it looks like
 ---------------------------- ------------------------------------------
 1. Wrong technique You picked BFS when you needed Dijkstra.
 You used greedy when the problem needed DP.
 The algorithm doesn't solve the problem.

 2. Right technique, You knew it was a segment tree problem,
 wrong implementation but your merge function was wrong, or you
 forgot to push lazy updates.

 3. Edge case n=1, empty array, all elements equal,
 single-node tree, disconnected graph.
 Your solution works on "normal" inputs.

 4. Complexity misjudgment You thought O(n log^2 n) would pass with
 n=2*10^5. It didn't. Or you used a map
 where an array would have been 5x faster.

 5. Misread problem You missed "strictly less than." You
 assumed 1-indexed when it was 0-indexed.
 You didn't see "modulo 10^9+7."

 6. Overflow int instead of long long. Intermediate
 multiplication exceeding 64 bits. Negative
 values wrapping around.

 7. Off-by-one Loop runs one iteration too many or too
 few. Array index off by 1. Binary search
 returns wrong boundary.

 8. Not enough testing You submitted on samples alone. A 2-minute
 stress test or a single hand-crafted edge
 case would have caught it.

Over time, you'll notice that 60-70% of your entries cluster in 2-3 categories. That's the signal.


Pre-Filled Common Mistakes by Technique

These are real mistakes that cost real people real rating. If you've competed for more than a few months, you'll recognize several of them as old friends.

The classic l vs r return confusion. You've written the binary search, it's logically correct, but you return r when you should return l (or vice versa). Cost me 45 minutes and 2 penalties on Div2C. The fix is to commit to one invariant and never deviate: "at all times, l is the last value where the predicate is false, r is the first where it's true." Then the answer is always r. Write this as a comment above every binary search you code.

Wrong predicate monotonicity. Your check function returns true, true, false, true, false -- it's not monotonic, so binary search doesn't apply. But you don't notice because it passes the samples. It only fails on test 37. Before writing binary search, explicitly verify: "if check(x) is true, is check(x+1) also true (or the reverse)?" If you can't prove it in 30 seconds, binary search is the wrong tool.

Integer overflow in mid calculation.(l + r) / 2 overflows when l and r are both large. Use l + (r - l) / 2. This one shouldn't still be catching people, but it does -- especially when binary searching on answers up to 1018.

Off-by-one in the search space bounds. You binary search on [1, n] but the answer could be 0. Or you search on [0, n-1] but the answer could be n. Forgetting to include the boundary value is a guaranteed WA that passes every sample.

Segment Tree

Array sized n instead of 4*n. In a recursive segment tree, the internal array needs 4*n elements, not 2*n or n. This causes a buffer overwrite that produces correct output on small tests and garbage on large ones. Segfault if you're lucky, wrong answer if you're not. Always vector<int> tree(4 * n).

Missing pushdown in lazy propagation. You implement lazy updates, test on samples, submit -- WA. The bug: you query a node without first pushing its pending update to children. Every function that touches a node's children must call push(v) first. Every. Single. One. Including the build function if you're not careful.

Wrong merge function. Your segment tree stores maximums but your merge does addition. Or you're merging (sum, max) pairs and the merge of two children's max values isn't max(left.max, right.max) because the max might span the boundary. The merge function is the heart of the segment tree -- get it wrong and every query is silently incorrect. Write the merge separately, test it on 2-element cases before building the tree.

DP

Wrong base case.dp[0] = 0 vs dp[0] = 1. This single bit determines whether every subsequent value is right or wrong. For counting problems: the empty set has exactly 1 way to be selected (do nothing). For optimization problems: the base case is often 0 or infinity, depending on min/max. Get this wrong and your answer is off by a factor or a constant on every single test case.

Wrong iteration order. In 0/1 knapsack, if you iterate capacity left-to-right, you're solving unbounded knapsack. The item gets used multiple times. This is the kind of bug that produces plausible-looking wrong answers -- close to correct but not quite. If your DP uses the current row's values to fill the current row, double-check whether that's intentional.

Forgetting the empty/zero case. The problem says "choose a non-empty subsequence." Your DP starts from index 0, which implicitly considers the empty subsequence. You get the wrong answer because you're counting (or optimizing over) a case the problem excluded. Always re-read: empty allowed? Zero-length strings? Subsets of size zero?

State space too small. You model the state as (index, sum) but the answer depends on (index, sum, last_element). Your DP is "correct" for the wrong problem. When you design the DP state, ask: "given this state, can I compute the answer without knowing anything else about how I got here?" If no, you're missing a dimension.

BFS / DFS

Not marking visited early enough in BFS. You mark nodes as visited when you pop them from the queue, not when you push them. This means the same node gets pushed multiple times, blowing up memory and time. For an n=105 grid, this turns O(n) BFS into O(n^2) or worse -- TLE on test 15. Mark visited when you enqueue, not when you dequeue.

Stack overflow in recursive DFS.n=106 nodes, tree is a long chain, you recurse. Stack depth exceeds the default limit (usually 1-8 MB). RE on test 8. Either increase the stack size (ulimit -s unlimited locally, but judges vary) or convert to iterative DFS. If the problem has n>105 and the graph could be a path, don't recurse.

BFS on a weighted graph. BFS finds shortest paths only when all edge weights are equal. If weights are 0 and 1, use 0-1 BFS (deque). If weights vary, use Dijkstra. Submitting BFS on a weighted graph gives WA on cleverly constructed tests where the shortest path isn't the one with fewest edges.

Forgetting to handle disconnected components. Your BFS/DFS starts from node 1 and never visits nodes in other components. Works on the sample (which is connected), fails on test 3. Always loop over all nodes: for (int i = 0; i < n; i++) if (!vis[i]) dfs(i);

Dijkstra

Using Dijkstra with negative edge weights. Dijkstra assumes that once a node is finalized, no shorter path to it exists. Negative edges violate this assumption. The algorithm "works" (doesn't crash) but produces wrong shortest distances. Use Bellman-Ford or SPFA instead. Cost me a problem on Edu Round when I didn't notice a single edge with weight -1 buried in the constraints.

Not using a visited/finalized check. You pop a node from the priority queue and process it, even if you've already found its shortest distance. Without the if (d > dist[v]) continue; check, you process the same node O(m) times. On dense graphs, this turns O(m log n) into O(m^2 log n). The algorithm still gives correct answers -- just too slowly. TLE on test 20.

Priority queue with wrong ordering. C++ priority_queue is a max-heap by default. For Dijkstra you need a min-heap: priority_queue<pii, vector<pii>, greater<pii>>. Using the default gives you longest paths. This is embarrassing, it's hard to debug because the samples might still pass if they have only one path.

DSU (Disjoint Set Union)

Forgetting path compression or union by rank. Without these, DSU operations are O(n) in the worst case. A chain of unions without path compression means find() traverses the entire chain every time. On n=105 with 105 queries -- TLE. Always implement both: parent[x] = find(parent[x]) for path compression, merge smaller into larger for union by rank.

Using parent[x] instead of find(x). You check if (parent[a] == parent[b]) instead of if (find(a) == find(b)). Without path compression having been called on a recently, parent[a] might point to an intermediate node, not the root. This gives wrong answers for "are these in the same component?" queries -- but only sometimes, making it maddening to debug.

Merging in the wrong direction. You do parent[rootA] = rootB when rootB's tree is smaller. This defeats the purpose of union by rank and degrades to O(n) per find. Always merge the smaller tree into the larger one. Store sizes, compare them, point the smaller root to the larger root.

Two Pointers

Wrong termination condition. You write while (l < r) when you need while (l <= r), or vice versa. The off-by-one means you miss the last valid window or process an invalid one. For sliding window problems, trace through a 3-element example by hand before coding. Seriously. Every time.

Not handling duplicates. The array has repeated elements. Your two-pointer skips over them (or doesn't skip when it should), producing duplicate answers or missing valid ones. Classic example: 3Sum -- forgetting while (l < r && a[l] == a[l-1]) l++ after finding a valid triple. Costs you WA on the test with all elements equal.

Moving the wrong pointer. You advance r when you should advance l. The window grows when it should shrink. The algorithm still terminates (eventually), but the answer is wrong. Before coding, write out in comments: "advance l when [condition], advance r when [condition]."

Greedy

No proof that greedy works. You have an intuition. It feels right. You code it, it passes samples, you submit -- WA on test 7. Greedy algorithms require proof. At minimum, convince yourself with an exchange argument: "if the optimal solution doesn't follow my greedy rule, I can swap two elements to make it better, contradiction." If you can't articulate this in 60 seconds, it's probably DP.

Wrong sorting criterion. You sort by deadline when you should sort by deadline minus duration. You sort by value when you should sort by value-to-weight ratio. The difference between AC and WA in greedy problems is almost always in the comparator. Write the comparator as a separate lambda and stare at it for 30 seconds before proceeding.

Greedy in the wrong direction. You process items left-to-right when right-to-left gives the correct answer (or vice versa). Classic: scheduling problems where processing deadlines in reverse order is correct but forward order isn't. If your greedy gives WA, try reversing the processing order before switching to DP.

Modular Arithmetic

Negative mod result. In C++, -7 % 3 is -1, not 2. If your formula can produce negative intermediate values, the mod result is negative, and everything downstream is wrong. Always use ((a % MOD) + MOD) % MOD or write a safe mod() function. This bug has cost more cumulative rating points across all competitors than any algorithm could recover.

Overflow in intermediate multiplication.a * b % MOD overflows if a and b are both close to 109 -- their product exceeds int range. Even with long long, two numbers close to 1018 overflow. Use (__int128)a * b % MOD or break into steps. Lost a Div2D to this -- the answer was correct for all samples because the sample values were small.

Wrong modular inverse. You compute the inverse of a modulo MOD using Fermat's little theorem (a^(MOD-2)), but MOD isn't prime. Or a is 0 and you divide by its "inverse." Or you use the inverse when you should just multiply. Modular inverse is only valid when gcd(a, MOD) = 1. Check before using.

Suffix Array / String Hashing

Hash collision giving WA. You use a single hash and two different strings happen to hash the same way. Works on 99.9% of tests, fails on the one the problem setter designed to break single hashes. Always use double hashing (two different base/mod pairs) or at minimum a large prime mod (>1018). The time cost is negligible. The WA cost isn't.

Wrong base or modulus choice. Base = 26 with lowercase strings -- but the problem has uppercase too. Or mod = 109+7, which is common enough that anti-hash tests exist for it on Codeforces. Use an uncommon prime and a base larger than the alphabet size. Something like base = 131, mod = a random large prime.

Off-by-one in substring hash extraction. You compute hash(s[l..r]) as h[r+1] - h[l] * pow[r-l+1] but get the power exponent wrong. Off by one in the exponent shifts every hash by one character. The formula is fragile -- derive it from scratch every time and verify on a 3-character example.

Graph Construction

1-indexed vs 0-indexed mismatch. The problem is 1-indexed, your adjacency list is 0-indexed. You read edge (u, v), store it in adj[u] and adj[v], but your array has size n instead of n+1. Buffer overwrite. Works on small tests, crashes or gives garbage on large ones. Decide your convention before reading input and be consistent.

Storing one direction of an undirected edge. The problem says "undirected" but you only add adj[u].push_back(v) and forget adj[v].push_back(u). Your BFS/DFS doesn't traverse the full graph. This usually fails on test 2 or 3 -- at least it's fast to catch. But under contest pressure, you'd be surprised how often it slips through.

Not clearing the graph between test cases. Multi-test problem. You build the adjacency list for test case 1, then for test case 2 you push more edges onto the same list without clearing it. Test case 2 sees a graph that's the union of both inputs. WA on the first multi-test problem you submit. Add adj[i].clear() or re-initialize the vector for every test case.

Sorting

Unstable sort when stability matters.std::sort is not stable -- equal elements can be reordered. If your logic depends on preserving the original order among ties (e.g., "sort by value, break ties by original index"), use std::stable_sort or include the index in the comparison. Otherwise your output is non-deterministic and fails on tests where tie-breaking matters.

Wrong comparator that violates strict weak ordering. Your comparator returns true for a < b AND b < a (e.g., return a.x <= b.x). This is undefined behavior -- std::sort assumes strict weak ordering and can segfault or infinite-loop. Always use <, never <=, in comparators. This one is insidious because it works on most inputs and crashes on specific permutations.

Sorting indices when you should sort values (or vice versa). You want to process elements in sorted order but also need original indices. You sort the array directly and lose the indices. Or you sort an index array but forget to dereference through it. Decide before coding: am I sorting a vector<int> or a vector<int> of indices with a custom comparator?

Bit Manipulation

Signed right shift on negative numbers.(-1) >> 1 is implementation-defined in C++. On most platforms, it's still -1 (arithmetic shift), not the large positive number you expected. Use unsigned types or cast explicitly when doing bit operations on potentially negative values.

Assuming int is 32 bits wide.1 << 31 is undefined behavior for int in C++ (sign bit). 1 << 32 is also undefined. Use 1LL << k for shifts of 32 or more bits. Bitmask DP with n=20 needs 1 << 20 = 1048576 -- fine for int. But n=25 needs 1 << 25 = 33554432 -- still fits, but 1 << 30 is already dangerous without LL.

Wrong mask for extracting/setting bits. You write x & (1 << k) to test bit k, but use the result as a boolean. In C++ this works (nonzero = true), but if you use it arithmetically (adding it to something), you're adding 2k instead of 1. Use (x >> k) & 1 to get a clean 0 or 1.

Network Flows

Missing reverse edges. Every edge in a flow network needs a reverse edge with capacity 0 (for the residual graph). You add forward edges but forget the reverse, so augmenting paths can't "undo" flow. The algorithm terminates with a suboptimal flow. Always add edges in pairs: forward with capacity c, reverse with capacity 0, and store their indices so you can find the reverse.

Wrong capacity in the flow graph. The problem maps to max-flow, but you set the wrong capacities. Undirected edge? Each direction gets its own edge pair. Node capacity? Split the node into in-node and out-node connected by an edge. Getting the graph construction wrong is the #1 source of flow bugs -- the algorithm itself is usually correct.

Using max-flow when you need min-cost max-flow (or vice versa). The problem asks for minimum cost. You implement max-flow and it gives the right flow value but wrong cost because you didn't track costs on edges. Or you implement MCMF for a problem where basic max-flow suffices, wasting 40 minutes on unnecessary complexity. Read the problem statement one more time before choosing the flow variant.

Geometry

Floating-point comparison with ==.if (a == b) where a and b are doubles that should be equal but differ by 1015 due to floating-point arithmetic. Use if (abs(a - b) < EPS) with an appropriate epsilon (109 is common). Better yet, use integer arithmetic wherever possible -- cross products with long long avoid the issue entirely.

Not handling degenerate cases. Three collinear points. A polygon with zero area. Two identical points defining a "line." Geometry code that works for general position breaks spectacularly on degenerate inputs. The problem setter knows this and always includes such test cases. Check: what happens when your cross product returns exactly 0?

Wrong orientation convention. Your convex hull assumes counterclockwise orientation but your cross product returns positive for clockwise. Or your point-in-polygon test uses the wrong sign convention. Pick one convention, document it in a comment, and verify it on a simple triangle before writing more code.


Patterns to Watch For

After 20-30 entries in your journal, step back and look at the meta-patterns. These tell you where to focus your practice -- not which algorithms to learn, but which habits to change.

"I keep making the same type of mistake"

If more than 30% of your entries share a root cause category, that's not carelessness -- it's a skill gap wearing a disguise.

 Root cause frequency What it means
 ------------------------- --------------------------------------
 Off-by-one dominant You don't have a systematic approach
 to boundary handling. Fix: spend a
 focused session on invariant-based
 binary search and loop bounds.

 Overflow dominant You don't have an automatic "overflow
 check" step. Fix: add "check types"
 to your pre-submit checklist.

 Wrong implementation You're coding from fuzzy memory, not
 dominant from understanding. Fix: re-derive
 and re-implement each structure from
 scratch until it's muscle memory.

The fix is never "try harder" or "be more careful." The fix is a focused practice session -- 2 hours, 5-10 problems, all targeting that specific weakness. One session converts a recurring mistake into a reflex.

"I keep misreading problems"

This is a process problem, not a knowledge problem.

 Before coding, write down -- on paper or in a comment:

 1. What is the INPUT? (types, ranges, constraints)
 2. What is the OUTPUT? (exact format, modular? count? construct?)
 3. What is the TRICK? (what makes this harder than it looks?)

If you can't fill in all three, you haven't understood the problem yet. Do not start coding. Re-read. Underline the constraints. Paraphrase the problem in your own words. This adds 2-3 minutes of reading time and saves 15-20 minutes of debugging a solution to the wrong problem.

"I keep TLEing"

You're not estimating complexity before coding. This is a discipline issue.

 Before writing a single line of code:

 1. What is n? (read the constraints)
 2. What is my algorithm's complexity?
 3. Does (2) fit within (1)?

 Quick reference:
 n <= 10^6 --> O(n) or O(n log n)
 n <= 10^5 --> O(n log n) or O(n sqrt(n)) maybe
 n <= 5000 --> O(n^2)
 n <= 500 --> O(n^3)
 n <= 20 --> O(2^n * n)

If your algorithm doesn't fit, stop coding and find a better one. Implementing a too-slow algorithm and "hoping it passes" is not a strategy. It's a waste of 20 minutes and a penalty.

"My bugs are always in the boring parts"

You're spending mental energy on the interesting algorithmic core and autopiloting through input reading, graph construction, output formatting. But the boring parts are where most bugs live.

Fix: treat boilerplate as real code. Read edges? Check: directed or undirected? 0 or 1-indexed? Multiple test cases -- did you clear everything? Output format -- newline or space separated? These take 30 seconds to verify and save 15 minutes.

"I solve problems in practice but not in contests"

The gap between practice and contest performance is almost always one of these:

  1. Time pressure changes your process. You skip the steps that catch bugs (complexity estimation, edge case testing, re-reading the problem). Solution: make those steps mechanical -- a checklist you follow every time, contest or not.

  2. You pick the wrong problem to work on. You spend 40 minutes on D when C was solvable in 15. Solution: read all problems first, estimate difficulty, do them in your-difficulty order, not A-B-C-D order.

  3. You tilt after a WA. The first wrong submission triggers panic, which causes random code changes, which cause more wrong submissions. Solution: the debugging protocol exists specifically for this moment.


Self-Test

  • [ ] Start your journal. Add your last 3 contest mistakes using the entry template above. If you can't remember the details, that itself is a lesson -- start recording immediately after each contest.

  • [ ] Categorize your last 10 wrong submissions on Codeforces into the 8 root cause categories. Which category appears most? That's your homework for this week.

  • [ ] Before your next contest, read your last 10 entries. After the contest, note whether any of the same categories appeared in your new mistakes. If yes, your pre-contest review isn't translating to changed behavior yet -- make the review more specific.

  • [ ] Pick one technique from the pre-filled section where you've been burned before. Solve 3 problems in that technique this week, specifically targeting the mistake pattern described.

  • [ ] Set a goal: reduce your most common root cause category from 30%+ of entries to under 15% over the next 10 contests. Track it. If it's not dropping, the practice isn't targeted enough.


The best competitive programmers don't make fewer mistakes. They make different ones every time.This journal is how you stop making the same mistake twice.


See also: Reflections | Debugging Under Pressure | CF Tips and Workflow | Speed Drills

Built for the climb from Codeforces 1100 to 2100.