What Is NP-Hard? The Class of Problems Every Programmer Should Know

- NP-hard problems are at least as hard as the hardest problems in NP: no polynomial-time algorithm is known to solve them exactly for all inputs.
- NP-complete is the stricter class: both NP-hard and in NP, meaning solutions can be verified in polynomial time. TSP, Subset Sum, and Graph Coloring are canonical examples.
- The P vs NP question asks whether every verifiable problem can also be solved in polynomial time. It remains unsolved and carries a $1 million Clay Mathematics prize.
- Pseudo-polynomial DP solves Subset Sum and 0/1 Knapsack in O(n·W) when the target value W is small, making them practically tractable despite being NP-hard in general.
- Bitmask DP handles TSP exactly in O(n²·2ⁿ), feasible for n ≤ 20 and billions of times faster than the naive O(n!) brute force.
- In a coding interview, a small n constraint with an exponential-looking problem signals O(2ⁿ) expected complexity: reach for bitmask DP, not a polynomial algorithm.
- Naming a problem as NP-hard is a strong signal of theoretical awareness that interviewers explicitly score when evaluating constraint analysis.
You've been staring at a problem for twenty minutes. You've tried sorting, tried hashing, tried every trick you know. The brute force works but it's O(2^n) and the constraints say n can be 10,000. Something feels different about this problem. Not hard in the "think harder" way. Hard in the "this might be fundamentally impossible to solve fast" way.
That instinct has a name. These are NP-hard problems, and they come with a formal definition that changes how you approach them.
Solving vs. Verifying: The Asymmetry at the Heart of It
Start with the Traveling Salesman Problem. Given a complete tour of all 50 US state capitals, can you tell me in a few seconds whether it's the shortest possible route?
No. You'd have to compare it against every other possible route, and there are 49! of them (roughly 6 × 10^62). But the question reverses: hand me a specific tour with a claimed total distance, and I can verify it in linear time. Just add up the legs.
This asymmetry between verifying a solution and finding one is the engine of NP-hardness.
Computer scientists formalized this into three classes:
- P: problems solvable in polynomial time. Binary search, shortest path, sorting.
- NP: problems where a candidate solution can be verified in polynomial time. You might not be able to find the solution fast, but you can check one fast.
- Every problem in P is also in NP. If you can solve something in polynomial time, you can obviously verify it in polynomial time.
The open question: can every problem in NP also be solved in polynomial time? That's P vs. NP. Nobody knows.
NP-Hard Is Not the Same as NP
A problem is NP-hard if every problem in NP can be reduced to it in polynomial time. Translation: if you could solve this problem quickly, you'd be able to solve everything in NP quickly. It's at least as hard as the hardest problems in NP.
Notice what that definition does not say. NP-hard problems don't have to be in NP themselves. They don't need to have verifiable solutions. NP-hardness is purely a lower bound on difficulty.
NP-complete is the stricter club: a problem is NP-complete if it's both NP-hard and in NP. The decision version of TSP ("is there a tour of cost ≤ k?") is NP-complete: you can verify a "yes" answer by checking the tour. The optimization version ("find the shortest tour") is NP-hard but arguably not in NP, because a "yes" answer requires proving no shorter route exists.
In practice, when engineers say "this problem is NP-hard," they usually mean NP-complete. The distinction matters theoretically; in interviews it rarely does.

Classic NP-Hard Problems You'll Recognize
Stephen Cook's 1971 Cook-Levin theorem proved that the Boolean satisfiability problem (SAT) is NP-complete. That single result was the first domino: if SAT can be solved in polynomial time, then P = NP and every NP problem falls with it.
Richard Karp built on that in a landmark 1972 paper, proving that 21 classic combinatorial problems are all NP-complete via polynomial-time reductions from SAT. The list reads like every problem you've ever found stubborn:
| Problem | What it asks |
|---|---|
| 3-SAT | Does a Boolean formula (3 literals per clause) have a satisfying assignment? |
| Subset Sum | Does any subset of integers sum to exactly T? |
| Traveling Salesman (decision) | Is there a tour visiting all vertices with cost ≤ k? |
| Graph Coloring (k ≥ 3) | Can vertices be colored with k colors so no adjacent vertices share one? |
| Hamiltonian Cycle | Does the graph have a cycle visiting every vertex exactly once? |
| 0/1 Knapsack (decision) | Can you select items totaling value ≥ V without exceeding weight W? |
The key mechanism is polynomial-time reduction. To prove problem X is NP-hard, you take a known NP-hard problem (say 3-SAT) and show that any instance of 3-SAT can be converted to an instance of X in polynomial time. If X could be solved fast, 3-SAT could be solved fast, which would mean P = NP. So X must be at least as hard as 3-SAT.
Reductions are directional and counterintuitive: you reduce from the hard problem to your problem. Reducing 3-SAT to X says "X is at least as hard as 3-SAT," not "X is easy because you turned it into something else."
The Million Dollar Question
Whether P = NP is the most important unsolved problem in theoretical computer science. The Clay Mathematics Institute listed it as one of seven Millennium Prize Problems in 2000, each worth one million dollars. None have been claimed.
Most researchers believe P ≠ NP. The argument is inductive: nobody has found a polynomial-time algorithm for SAT in the 50+ years since Cook's theorem, despite enormous motivation. The entire field of modern cryptography assumes P ≠ NP. RSA encryption would collapse if someone could factor large integers in polynomial time, which they could if P = NP.
Believing P ≠ NP and proving it are completely different things. Mathematical tools that would suffice for the proof don't yet exist. The best mathematical minds in the world have been stuck on this for half a century, and the prize is still sitting there.

The face of every CS theorist who thought they'd solve P vs. NP by Tuesday.
NP-Hard Doesn't Mean Give Up
It means pick your weapon based on the constraints.
Weapon 1: Pseudo-polynomial DP for small numeric values.
Subset sum and 0/1 Knapsack are NP-hard in general, but both have dynamic programming solutions that run in O(n · W) time, where W is the target sum or weight capacity. When W is small (say, ≤ 10^6), this is fast enough. The term for this is pseudo-polynomial: polynomial in the numeric value of the input, not in the number of bits representing it.
def subset_sum(nums: list[int], target: int) -> bool: dp = {0} for num in nums: dp = dp | {s + num for s in dp} return target in dp
Weapon 2: Bitmask DP for small n.
TSP with n ≤ 20 is tractable via bitmask DP. State dp[mask][v] stores the minimum cost to reach vertex v having visited exactly the cities in mask:
def tsp(dist: list[list[int]]) -> int: n = len(dist) INF = float('inf') dp = [[INF] * n for _ in range(1 << n)] dp[1][0] = 0 # start at city 0 for mask in range(1 << n): for u in range(n): if dp[mask][u] == INF: continue if not (mask >> u & 1): continue for v in range(n): if mask >> v & 1: continue next_mask = mask | (1 << v) dp[next_mask][v] = min(dp[next_mask][v], dp[mask][u] + dist[u][v]) full = (1 << n) - 1 return min(dp[full][v] + dist[v][0] for v in range(1, n))
Time is O(n² · 2^n). For n = 20, that's about 400 million operations. Compare that to the naive O(n!) on 20 cities: 2.4 × 10^18 operations. The bitmask DP is six billion times faster while still being exact.
Weapon 3: Greedy approximations for large n.
When n is too large for exact methods, give up optimality and settle for a guarantee. For metric TSP (where the triangle inequality holds), building a minimum spanning tree and doing a depth-first traversal gives a tour at most twice the optimal. That's a 2-approximation. Greedy algorithms for set cover produce solutions within a factor of ln(n) of optimal.
Weapon 4: Backtracking with pruning.
For search-tree problems, backtracking with branch-and-bound often handles practical instances much faster than worst-case analysis suggests. Prune branches the moment they exceed your current best solution. Real inputs rarely hit worst-case structure.
The Constraint Block Is a Message
NP-hard problems show up in interviews more than most engineers expect. They come slightly disguised, and a small constraint n is the tell.
"Find the minimum cost to visit all nodes in a graph." n ≤ 15. That's TSP via bitmask DP. "Can any subset of these integers sum to T?" n ≤ 25. That's subset sum via bitmask meet-in-the-middle or DP. "Color these tasks so no conflicting tasks share a time slot." Small n. Graph coloring via backtracking.
n ≤ 20 with an exponential-looking problem means O(2^n) or O(n · 2^n) is the expected complexity. n ≤ 10^5 with similar framing means an exact solution isn't expected, and a greedy heuristic or approximation is the right direction.
What interviewers want to see, in order:
- Name the problem. "This looks like a variant of TSP, which is NP-hard." Showing theoretical awareness is a strong signal.
- Ask for constraints before writing a single line of code. The answer tells you which algorithm class applies.
- State the brute force first. O(2^n) for subset sum, O(n!) for TSP. Then optimize.
- Apply the right technique for the given n. Bitmask DP for small n, pseudo-polynomial DP for small values, greedy approximation for large n.
The one thing that fails: claiming you can solve TSP exactly in polynomial time. That's a hard red flag. Either you missed that it's NP-hard, or you're overclaiming.

When the interviewer says "n ≤ 15, visit all nodes" and your brain loads the wrong algorithm.
You can practice recognizing these patterns under real interview conditions at SpaceComplexity, which runs voice-based mock interviews with rubric-graded feedback on constraint analysis, algorithm selection, and how clearly you explain your reasoning under pressure.