The Journey

I started competitive programming in 2021 with zero algorithmic knowledge. Fast forward to today: 200+ problems solved across Codeforces, LeetCode, CodeChef, and AtCoder.

This isn’t a “how I became red on Codeforces” story. I’m still grinding through Div 2 problems. But I’ve learned a lot about problem-solving, pattern recognition, and how to think algorithmically.

Here’s what actually stuck.

1. Most Problems Are Variations of ~20 Patterns

After solving 50 problems, I thought every problem was unique. After 200, I realized they’re all remixes.

The Core Patterns

Sliding Window — Any “subarray with property X” problem

int maxSubarraySum(vector<int>& nums, int k) {
    int sum = 0, maxSum = 0;
    for (int i = 0; i < nums.size(); i++) {
        sum += nums[i];
        if (i >= k) sum -= nums[i - k];
        maxSum = max(maxSum, sum);
    }
    return maxSum;
}

Two Pointers — Sorted array problems, palindrome checks

bool isPalindrome(string s) {
    int l = 0, r = s.size() - 1;
    while (l < r) {
        if (s[l++] != s[r--]) return false;
    }
    return true;
}

Prefix Sums — Range query problems

vector<int> prefixSum(vector<int>& arr) {
    vector<int> prefix(arr.size() + 1);
    for (int i = 0; i < arr.size(); i++) {
        prefix[i + 1] = prefix[i] + arr[i];
    }
    return prefix;
}

Once you internalize these, you stop seeing “new” problems. You see patterns.

2. Brute Force First, Optimize Later

Early on, I’d stare at a problem for 30 minutes trying to find the optimal solution. Waste of time.

Better approach:

  1. Write the brute force solution (even if it’s O(n³))
  2. Get it accepted on small test cases
  3. Identify the bottleneck
  4. Optimize that specific part

Example: “Find longest substring without repeating characters”

Brute force (O(n³)):

int lengthOfLongestSubstring(string s) {
    int maxLen = 0;
    for (int i = 0; i < s.size(); i++) {
        for (int j = i; j < s.size(); j++) {
            if (hasUniqueChars(s.substr(i, j - i + 1))) {
                maxLen = max(maxLen, j - i + 1);
            }
        }
    }
    return maxLen;
}

Optimized (O(n)):

int lengthOfLongestSubstring(string s) {
    unordered_set<char> seen;
    int l = 0, maxLen = 0;
    for (int r = 0; r < s.size(); r++) {
        while (seen.count(s[r])) {
            seen.erase(s[l++]);
        }
        seen.insert(s[r]);
        maxLen = max(maxLen, r - l + 1);
    }
    return maxLen;
}

The brute force version helped me understand the problem. The optimization came naturally after.

3. Draw It Out

Seriously. Pen and paper.

I wasted hours debugging problems that would’ve been obvious if I’d just drawn the state transitions.

Example: Binary tree problems become trivial when you draw the tree and trace the recursion.

Graph problems? Draw the graph. Mark visited nodes. Trace the DFS/BFS path.

DP problems? Draw the DP table and fill it in manually for a small example.

If you can’t draw it, you don’t understand it.

4. Time Complexity Intuition

After enough problems, you develop a sense for what complexity you need:

n (input size) Max Complexity Algorithm Type
n ≤ 10 O(n!) Permutations, backtracking
n ≤ 20 O(2ⁿ) Bitmask DP, subset enumeration
n ≤ 500 O(n³) Floyd-Warshall, DP with 3 states
n ≤ 5,000 O(n²) Nested loops, simple DP
n ≤ 100,000 O(n log n) Sorting, binary search, segment trees
n ≤ 1,000,000 O(n) Hash maps, two pointers, prefix sums

This table saved me from going down wrong paths countless times.

5. Debug by Simplifying

When your solution fails on test case 47 of 50, don’t stare at the code.

Instead:

  1. Create the smallest input that breaks your code
  2. Trace through your logic manually
  3. Find where your assumption breaks

Example: My binary search was failing because I wrote mid = (l + r) / 2 instead of mid = l + (r - l) / 2. Integer overflow on large inputs. Took me 2 hours to find.

Lesson: Test edge cases early. INT_MAX, INT_MIN, empty arrays, single elements.

6. Consistency > Intensity

I tried the “grind 10 problems a day” approach. Burned out in a week.

What actually worked:

  • 2-3 problems per day, every day
  • Focus on understanding, not speed
  • Review solutions even when you get AC

The goal isn’t to solve problems fast. It’s to build pattern recognition that lasts.

7. Read Others’ Solutions

After solving a problem, I’d check the top-rated solutions on Codeforces or LeetCode discussions.

What I learned:

  • Cleaner ways to implement the same logic
  • Edge cases I missed
  • Alternative approaches I didn’t consider

Sometimes the “optimal” solution is just a cleaner version of what you wrote. Sometimes it’s a completely different algorithm.

Both are valuable.

What’s Next?

I’m nowhere near expert level. Still struggling with:

  • Advanced graph algorithms (max flow, min cut)
  • Complex DP states (bitmask DP, digit DP)
  • Geometry problems (I hate geometry)

But I’m better than I was 200 problems ago. And that’s the point.

If you’re just starting out: don’t compare yourself to red coders. Compare yourself to who you were last month.

Solve problems. Learn patterns. Repeat.


Currently grinding on Codeforces and LeetCode. Let’s connect if you’re on the same journey.