The argument against LeetCode in professional circles goes something like this: "You'll never actually implement a Red-Black tree in production. Stop wasting time on toy problems."
The argument for it is usually weaker: "Big tech companies require it."
Neither argument is quite right. Here's what 300+ problems and multiple production projects have actually taught me about where algorithm thinking matters in the real world — and where it genuinely doesn't.
Where It Matters: Database Query Design
The most direct application of algorithm thinking I encounter isn't in code — it's in SQL queries.
Consider fetching the "top 5 contributors" from a database of activity events. A naive approach pulls all events, groups them in application code, and sorts in memory:
// Pulls thousands of rows into memory to sort in Node.js
const events = await db.event.findMany({ where: { projectId } });
const counts = events.reduce((acc, e) => {
acc[e.userId] = (acc[e.userId] ?? 0) + 1;
return acc;
}, {});
const top5 = Object.entries(counts)
.sort((a, b) => b[1] - a[1])
.slice(0, 5);
LeetCode taught me to ask: what's the time and space complexity, and is the expensive operation happening at the right layer? This version pushes the aggregation and sort into PostgreSQL, where it happens in a single pass with index support:
SELECT user_id, COUNT(*) as event_count
FROM events
WHERE project_id = $1
GROUP BY user_id
ORDER BY event_count DESC
LIMIT 5;
The database aggregates millions of rows in milliseconds with a sequential scan that never touches the application server's memory. The Node.js version would timeout on a large dataset. This is O(n log n) vs O(n) — and knowing why it matters is the direct product of understanding sorting algorithms.
Where It Matters: Caching and Lookups
When building the industry insights dashboard in Mentora, I needed to quickly check whether a user's listed skills intersected with in-demand skills for their target role.
A naive approach converts one list to an array and checks .includes() for each skill — O(n²).
A Set lookup is O(1):
// Slow — O(n²) for large skill lists
const missingSkills = userSkills.filter(
(skill) => !inDemandSkills.includes(skill),
);
// Fast — O(n) with Set construction
const inDemandSet = new Set(inDemandSkills);
const missingSkills = userSkills.filter((skill) => !inDemandSet.has(skill));
I know to reach for a Set because I've solved 50 problems where the key insight was "if you need fast lookups, use a hash set." The concept transferred directly.
Where It Matters: Graph Problems in Disguise
CI Healer's dependency analysis is, under the hood, a directed graph problem. When a failing test has multiple causes — a dependency conflict that triggered a type error that caused a test import to fail — tracing the root cause means traversing a dependency tree and finding the origin of the failure cascade.
I implemented a depth-first search through the error cause chain:
def find_root_cause(error: Error, visited: set = None) -> Error:
if visited is None:
visited = set()
if error.id in visited:
return error # Cycle detected — stop here
visited.add(error.id)
if not error.caused_by:
return error # Leaf node — this is the root
return find_root_cause(error.caused_by, visited)
I didn't have to think about whether DFS was the right approach. I've implemented it in a hundred variations. The application was obvious.
Where It Genuinely Doesn't Matter
Let me be honest about the other side.
Tree rotations in balanced BSTs — I've never once needed to implement a self-balancing tree in production. Every language runtime and database that needs a balanced tree uses its own implementation. Understanding that B-trees are used in database indexes is useful. Implementing one is not.
Dynamic programming on grid problems — LeetCode has hundreds of "minimum path in a grid" problems. I've never encountered this class of problem in application code. I encounter approximations of it in scheduling systems, but the actual DP solution is rarely the right approach there either.
Bit manipulation — I've used it once, in an embedded-adjacent context. Almost never in web or backend work.
Specific algorithm implementations — Merge sort, quicksort, Dijkstra's algorithm from scratch. Knowing what they do and their time complexity is valuable. Implementing them is generally not.
The Real Transferable Skill
The actual thing LeetCode teaches — when you're doing it seriously, not just memorising solutions — is a framework for thinking about performance before you have a problem.
It trains the question: "Before I write this, what's the time complexity, and is there a better approach?"
For most CRUD operations in web development, the answer is "it doesn't matter." A O(n²) operation on an array of 20 user preferences will never be your bottleneck.
But for queries that run on millions of rows, API endpoints that hit the same data on every request, or event processing pipelines that need to handle bursts — that instinct to ask the complexity question before you write the code is what separates implementations that scale from implementations that need emergency caching added three months after launch.
The Practical Conclusion
Grind LeetCode until the following become reflexes:
- When to use a hash map vs an array
- When a problem is a graph in disguise
- The difference between O(n), O(n log n), and O(n²) — and when it matters
- How to reduce a problem to simpler subproblems
Then stop worrying about the exotic stuff. In production, the bottleneck is almost never your algorithm. It's your database query plan, your cache invalidation strategy, or a sequential operation that should be parallel.
But the habit of thinking in terms of complexity before writing is genuinely useful every week. That's what the grinding is for.