Competitive programming is not only about knowing algorithms. A large part of problem solving is about learning how to think about a problem, identify the bottleneck, and gradually improve the solution.
In this blog, I want to share a simple approach that I use when solving competitive programming problems.
1. Understand the Problem Before Coding
The first mistake many beginners make is starting to code immediately after reading a problem.
Instead, ask yourself:
- What exactly is being asked?
- What are the constraints?
- What does the input represent?
- What should the output contain?
- Are there any special cases?
The constraints are particularly important.
For example, suppose:
1 ≤ N ≤ 20
An exponential solution such as O(2^N) might be completely acceptable.
But if:
1 ≤ N ≤ 2 × 10^5
then an O(2^N) or O(N²) solution will usually not work.
The constraints often tell you which algorithms are possible.
2. Start With the Simplest Solution
Before thinking about optimization, try to construct the most straightforward solution.
For example, if the problem asks:
Find whether an array contains two equal elements.
A simple approach is to compare every pair.
for(int i = 0; i < n; i++) {
for(int j = i + 1; j < n; j++) {
if(a[i] == a[j])
return true;
}
}
This takes O(N²) time.
The important thing is not whether this solution passes.
It helps us understand why the problem is expensive.
3. Identify the Bottleneck
Now ask:
Why is my solution slow?
In the previous example, we repeatedly compare elements.
Can we remember the elements that we have already seen?
Yes.
Using a set:
set<int> s;
for(int x : a) {
if(s.count(x))
return true;
s.insert(x);
}
The complexity becomes approximately:
O(N log N)
With unordered_set, the expected complexity becomes:
O(N)
This is the core optimization mindset:
Don't optimize everything. Find the part that is unnecessarily expensive.
4. Use Constraints to Select the Algorithm
A useful mental table is:
| Constraint | Possible Approaches |
|---|---|
N ≤ 20 | Bitmasking, Backtracking, O(2^N) |
N ≤ 100 | O(N³) may be possible |
N ≤ 500 | O(N²) |
N ≤ 2×10^5 | O(N log N) / O(N) |
N ≤ 10^6 | Usually O(N) |
| Very large values | Mathematical / logarithmic / greedy approaches |
These are not strict rules, but they provide a useful starting point.
5. Look for Patterns
After solving enough problems, you start noticing recurring patterns.
For example:
Arrays
Think about:
- Prefix sums
- Two pointers
- Sliding window
- Binary search
- Hashing
- Sorting
Graphs
Think about:
- BFS
- DFS
- Shortest path
- DSU
- Topological sorting
- Minimum spanning tree
Dynamic Programming
Ask:
Can the problem be divided into smaller states?
Then identify:
State
Transition
Base Case
Answer
Strings
Consider:
- Frequency counting
- Hashing
- Prefix function
- Z-function
- Two pointers
- Trie
Recognizing the pattern is often more important than memorizing the implementation.
6. Don't Ignore Edge Cases
A solution that works on the sample cases can still fail badly.
Before submitting, test:
- Minimum input
- Maximum input
- All values equal
- All values different
- Sorted input
- Reverse-sorted input
- Negative values
- Duplicate values
- Empty/small cases where applicable
For example:
n = 1
can expose assumptions that were never considered.
7. Complexity Matters
Always try to estimate both:
Time Complexity
How many operations are performed?
Examples:
O(N)
O(N log N)
O(N²)
O(2^N)
Space Complexity
How much additional memory is required?
For example:
vector<int> freq(100000);
uses additional memory proportional to the range of values.
Understanding complexity helps you decide whether an approach can survive the largest test case.
8. Learn From Wrong Answers
One of the most valuable things in competitive programming is a wrong submission.
Instead of simply changing the code until it passes, ask:
What assumption did I make that was incorrect?
For example:
Wrong Answer
↓
Find failing test
↓
Understand why it fails
↓
Identify incorrect assumption
↓
Modify approach
↓
Submit again
This process builds problem-solving ability much faster than simply reading solutions.
9. Learn From Other Solutions
After solving a problem, look at accepted solutions.
But don't just copy them.
Try to understand:
- Why did they choose this data structure?
- Why is this complexity sufficient?
- Is there a simpler implementation?
- Is there another way to derive the same solution?
Sometimes a 50-line solution can be reduced to 15 lines after understanding the underlying idea.
10. Build Your Own Template
Having a basic template can save time during contests.
For example:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
void solve() {
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while(t--) {
solve();
}
return 0;
}
However, a template should help you focus on the problem rather than become a collection of code that you don't understand.
11. Practice Consistently
You don't need to solve dozens of problems every day.
A better approach is:
Solve
↓
Get stuck
↓
Think
↓
Study the concept
↓
Solve
↓
Analyze
↓
Repeat
Consistency matters more than the number of problems solved in a single day.
12. Final Takeaway
Competitive programming is a gradual process.
You don't become better simply by memorizing more algorithms.
You improve by repeatedly asking:
Can I solve this more efficiently?
Start with a simple solution.
Find its bottleneck.
Use the constraints.
Identify the underlying pattern.
Optimize.
Then analyze what you learned.
Over time, problems that initially look completely unfamiliar start looking like combinations of patterns you have already seen.
That is when competitive programming becomes much more interesting.
Keep solving, keep experimenting, and most importantly, understand why your solution works.








why are you spamming ai slop dude?