Problem Constraints & Algorithmic Trade-offs
Understanding Problem Constraints — Input Size, Time Limits, Memory Limits
Read aloud is unavailable in this browser.
Constraints Are Clues, Not Obstacles
Every coding problem—whether in a competitive programming contest, a technical interview, or a real production system—comes with constraints. And here's a secret that separates novices from engineers:
Constraints aren't obstacles. They're clues.
A beginner looks at "n ≤ 10⁶" and feels limited. An engineer looks at the same constraint and immediately knows: "I need O(n log n) or better; O(n²) won't work here." The constraint tells you what algorithms are viable before you write a single line of code.
This module teaches you to read constraints like an engineer—extracting maximum information to guide your algorithmic decisions. By the end, you'll never dive into implementation without first asking: "What do the constraints tell me?"
By the end of this page, you will understand the three fundamental types of constraints (input size, time limits, memory limits), how to translate constraints into algorithmic requirements, and how to use a simple mental math framework to assess algorithm viability before implementation.
The Three Fundamental Constraints
Almost every computational problem operates under three fundamental constraints. Understanding each, and how they interact, is essential for algorithm selection.
1. Input Size Constraints
These specify the scale of data your algorithm must handle:
- "n ≤ 10⁵" — Array can have up to 100,000 elements
- "1 ≤ n ≤ 10⁶" — Up to 1 million elements
- "1 ≤ n, m ≤ 10³" — Two dimensions, each up to 1,000
- "String length ≤ 10⁴" — Up to 10,000 characters
- "Number of queries: Q ≤ 10⁵" — 100,000 operations to process
Input size is the most important constraint because it directly determines which complexity classes are feasible. The relationship is mathematical: if n = 10⁶ and your algorithm is O(n²), that's 10¹² operations—typically far too many.
2. Time Limit Constraints
These specify how quickly your solution must complete:
- "Time limit: 1 second" — Most common in competitive programming
- "Time limit: 2 seconds" — Slightly relaxed
- "Response time: < 100ms" — Real-time systems
- "Request must complete within 30 seconds" — Web services
- "Daily batch job, 8-hour window" — Overnight processing
Time limits translate to operation counts through a rule of thumb:
Modern computers execute roughly 10⁸ to 10⁹ simple operations per second.
This means:
- 1 second ≈ 10⁸ operations budget
- 2 seconds ≈ 2 × 10⁸ operations budget
- 100ms ≈ 10⁷ operations budget
These are approximations—actual speed depends on hardware, operation complexity, and memory access patterns—but they're accurate enough for algorithm selection.
3. Memory Limit Constraints
These specify how much RAM your solution may use:
- "Memory limit: 256 MB" — Typical competitive programming
- "Memory limit: 512 MB" — More generous
- "Must fit in L3 cache: 8 MB" — Performance-critical systems
- "Device has 128 MB RAM" — Embedded systems
- "No additional memory beyond input" — In-place algorithms required
Memory translates to element counts:
- 1 integer (4 bytes) → 256 MB ≈ 64 million integers
- 1 long/double (8 bytes) → 256 MB ≈ 32 million values
- 1 boolean (1 byte typically) → 256 MB ≈ 256 million booleans
- n×n matrix of integers → 256 MB ≈ n ≤ 8,000 for 2D array
Memory constraints often eliminate algorithms that require large auxiliary data structures.
| Constraint Type | What It Specifies | How It Guides Algorithms | Typical Values |
|---|---|---|---|
| Input Size (n) | Scale of data | Determines viable complexity classes | 10³ to 10⁹ |
| Time Limit | Execution time budget | Sets operation count ceiling | 1-5 seconds, 100ms-30s |
| Memory Limit | RAM budget | Limits auxiliary data structures | 128 MB - 1 GB |
The Input-Size-to-Complexity Mapping
This is the most important mental model in this entire module. Memorize it. Given an input size n and a time limit (usually ~1 second), there's a maximum complexity that's feasible:
The Constraint-Complexity Table:
| Input Size (n) | Max Feasible Complexity | Operations (approx) | Algorithm Examples |
|---|---|---|---|
| n ≤ 10 | O(n!), O(2^n) | ~3.6M, ~1K | Brute force permutations, subset enumeration |
| n ≤ 20 | O(2^n), O(n² × 2^n) | ~1M | Bitmask DP, meet-in-the-middle |
| n ≤ 100 | O(n³), O(n² log n) | ~1M | Floyd-Warshall, some DP problems |
| n ≤ 1,000 | O(n²) | ~1M | Bubble sort, 2D DP, adjacency matrix |
| n ≤ 10,000 | O(n²) barely, O(n√n) comfortable | ~100M | Optimized O(n²), Mo's algorithm |
| n ≤ 100,000 | O(n log n), O(n√n) | ~2M-3M | Merge sort, segment trees |
| n ≤ 1,000,000 | O(n log n), O(n) | ~20M | Linear algorithms, efficient sorting |
| n ≤ 10,000,000 | O(n) | ~10M | Linear scans, counting |
| n ≤ 10^8 | O(n) barely, O(log n) operations | ~100M | Simple linear, precomputation + O(1) queries |
| n ≤ 10^9 or more | O(log n), O(1) | ~30 or 1 | Binary search, math formulas |
When you see a constraint, immediately ask: 'What's n²?' If n = 10⁵, then n² = 10¹⁰—way over 10⁸. So O(n²) is too slow. You need O(n log n) or O(n). This 5-second calculation saves hours of debugging timeout errors.
How to use this table:
- Read the constraint: "Array size n ≤ 10⁵"
- Find target row: n ≤ 100,000 → O(n log n) is safe
- Eliminate algorithms: O(n²) comparison sort? Too slow.
- Select viable approaches: Merge sort, binary search-based solutions, divide and conquer
Example reasoning:
Problem: "Given an array of n integers (n ≤ 10⁵), find if any two elements sum to a target."
- n ≤ 10⁵ → need O(n log n) or better
- Brute force O(n²)? 10¹⁰ operations → too slow ❌
- Sort + two pointers O(n log n)? ~1.7M operations → fine ✓
- Hash set O(n)? ~10⁵ operations → even better ✓
The constraint eliminated the naive approach before you'd waste time implementing it.
Calculating Operation Budgets from Time Limits
The 10⁸ operations per second rule lets you translate time limits into operation budgets:
The calculation:
Operation Budget = Time Limit (seconds) × 10⁸
Examples:
- 1 second limit → 10⁸ operations
- 2 second limit → 2 × 10⁸ operations
- 0.5 second limit → 5 × 10⁷ operations
- 5 second limit → 5 × 10⁸ operations
Then check if your algorithm fits:
Given n = 10⁶ and time limit = 2 seconds:
- Budget = 2 × 10⁸ operations
- O(n) → 10⁶ operations → ✓ plenty of room
- O(n log n) → 10⁶ × 20 ≈ 2 × 10⁷ → ✓ comfortable
- O(n²) → 10¹² → ❌ 5000x over budget
- O(n sqrt(n)) → 10⁶ × 10³ = 10⁹ → ❌ 5x over budget (risky)
Safety margins:
The 10⁸ rule is an approximation. Account for:
- Constant factors: An O(n) algorithm might do 10 operations per element, effectively 10n
- Memory access: Cache misses can slow execution 10-100x
- Complex operations: Division, modulo, function calls are slower than addition
- Input/output: Reading large inputs can consume a significant portion of time
Conservative approach: Plan for 10⁷ to 5 × 10⁷ for safety, especially in competitive programming where time limits are tight.
Some operations are inherently slower: function calls, memory allocation, random memory access, I/O operations. An 'O(n)' algorithm that chases pointers across memory (like traversing a linked list) can be 10x slower than an 'O(n)' algorithm with sequential array access. Cache locality matters at scale.
Real-World Time Budget Calculation
Step-by-step analysis of whether an algorithm will pass within time limits:
Example 1
n = 500,000 elements, Time limit = 1 second, Proposed algorithm: O(n log n)Operations: 500,000 × 19 ≈ 9.5 million. Budget: 100 million. Verdict: SAFE (10x headroom)Explanation
log₂(500,000) ≈ 19. The algorithm uses less than 10% of the budget—plenty of margin for constant factors.
Example 2
n = 10,000 elements, Time limit = 2 seconds, Proposed algorithm: O(n²)Operations: 10,000² = 100 million. Budget: 200 million. Verdict: MARGINALExplanation
The algorithm just barely fits. A high constant factor or slow I/O could cause TLE (Time Limit Exceeded).
Example 3
n = 50,000 elements, Time limit = 1 second, Proposed algorithm: O(n²)Operations: 50,000² = 2.5 billion. Budget: 100 million. Verdict: IMPOSSIBLEExplanation
The algorithm exceeds the budget by 25x. No amount of optimization will make this work. Need a better algorithm.
Understanding Memory Limits
Memory limits are often overlooked by beginners, but they're just as important as time limits. Understanding memory helps you:
- Avoid "Memory Limit Exceeded" errors
- Choose between O(n²) space and O(n) space algorithms
- Decide when in-place algorithms are required
- Understand when precomputation is feasible
Memory calculation basics:
| Data Type | Size (bytes) | Items per 256 MB | Items per 1 GB |
|---|---|---|---|
| boolean (typical) | 1 | 256 million | 1 billion |
| char (ASCII) | 1 | 256 million | 1 billion |
| short (16-bit int) | 2 | 128 million | 512 million |
| int (32-bit) | 4 | 64 million | 256 million |
| long (64-bit) | 8 | 32 million | 128 million |
| double (64-bit) | 8 | 32 million | 128 million |
| pointer/reference | 8 | 32 million | 128 million |
| Java Integer (boxed) | 16+ | 16 million | 64 million |
| Python int (small) | 28+ | ~9 million | ~35 million |
Common memory scenarios:
Scenario 1: Linear array
- n = 10⁶ integers (4 bytes each)
- Memory = 4 × 10⁶ = 4 MB ✓ (easily fits in 256 MB)
Scenario 2: 2D matrix
- n × n matrix where n = 10,000
- Memory = 4 × 10,000 × 10,000 = 400 MB ❌ (exceeds 256 MB limit)
- For n × n in 256 MB: n ≤ √(64M) ≈ 8,000
Scenario 3: Adjacency matrix for graph
- n = 10,000 nodes, dense graph
- Memory = 4 × 10,000 × 10,000 = 400 MB ❌
- Need adjacency list instead: O(n + edges) space
Scenario 4: Memoization table
- DP with states n × m where n, m = 10,000
- Memory = 4 × 10⁸ = 400 MB ❌
- May need space optimization (keeping only recent rows)
For a 256 MB limit with 4-byte integers: n × n matrix fits only if n ≤ ~8,000. For 512 MB: n ≤ ~11,000. This constraint often forces you to use different representations (adjacency list vs matrix) or space-optimized DP techniques.
Hidden memory costs:
Languages have overhead beyond raw data:
- Object headers: Java objects have 12-16 bytes overhead each
- Pointer indirection: ArrayList<Integer> uses 20+ bytes per integer vs 4 for int[]
- Garbage collection: GC needs headroom; 256 MB limit may effectively be 180 MB usable
- Recursion stack: Each recursive call uses stack space (~100-1000 bytes per frame)
- Hash table overhead: Typically 20-50% space overhead for load factor < 1
In competitive programming, prefer primitive arrays. In production, balance memory with code clarity and maintainability.
Multi-Dimensional Constraints: Reading the Full Picture
Real problems often have multiple interacting constraints. Reading them carefully reveals exactly what's feasible.
Example 1: Array with queries
Given an array of n ≤ 10⁵ elements and Q ≤ 10⁵ queries, answer each query in time.
Analysis:
- Total operations = n (preprocessing) + Q × (query time)
- If query is O(n): total = 10⁵ + 10⁵ × 10⁵ = 10¹⁰ ❌
- If query is O(log n): total = 10⁵ + 10⁵ × 17 ≈ 1.8 × 10⁶ ✓
- If query is O(1) with O(n) preprocessing: total = 10⁵ + 10⁵ = 2 × 10⁵ ✓✓
The constraint pattern (large n AND large Q) signals: "You need O(log n) or O(1) per query."
Example 2: Graph problem
Given a graph with n ≤ 1000 nodes and m ≤ 100,000 edges...
- n² = 10⁶ — O(n²) algorithms are fine
- But O(m × n) = 10⁸ — borderline, might TLE
- Adjacency matrix? n² = 10⁶ entries × 4 bytes = 4 MB ✓
- Or adjacency list using O(n + m) ≈ 0.5 MB ✓
Both representations are feasible here because n is moderate.
Example 3: Heavy constraint disparity
Given n ≤ 100,000 integers, perform up to m ≤ 10 operations.
Key insight: m is tiny! Even O(n) per operation is fine:
- Total = 10 × 10⁵ = 10⁶ ✓
Don't over-engineer. When one dimension is small, simpler algorithms suffice.
| Constraint Pattern | What It Signals | Typical Approach |
|---|---|---|
| Large n, Large Q | Need fast queries | Preprocessing, segment trees, binary search |
| Large n, Small Q | Per-query can be slow | Simple O(n) or O(n log n) per query |
| Small n, Large anything | Exponential might work | Brute force, backtracking viable |
| n×m product bounded | Total work capped | Often O(n × m) is acceptable |
| Sum of all n ≤ X | Amortized budget | Total work across test cases is X |
Some problems say: 'Multiple test cases. Sum of n across all test cases ≤ 10⁶.' This means your O(n) algorithm can handle each case individually, even if some cases have large n—because total work is bounded. It's a hint that O(n) or O(n log n) is expected.
Constraints in Different Contexts
Constraints look different depending on where you encounter them. Learn to recognize them in each context.
Competitive Programming:
Explicit and precise:
Constraints:
1 ≤ n ≤ 10⁵
1 ≤ m ≤ 2 × 10⁵
Time limit: 1 second
Memory limit: 256 MB
These constraints are intentionally set to allow specific algorithm classes. Problem setters calibrate limits so that O(n log n) passes and O(n²) fails. Read them as hints!
Technical Interviews:
Usually conversational:
- Interviewer: "Assume the array has up to 10 million elements."
- Hidden constraint: You need O(n) or O(n log n)
- Interviewer: "This function is called thousands of times per second."
- Hidden constraint: Amortize expensive work; optimize the hot path
Always ask clarifying questions:
- "What's the expected input size?"
- "What's the acceptable latency?"
- "Is memory a concern?"
- "Is this called once or repeatedly?"
These questions show engineering maturity. Interviewers expect and appreciate them.
Real-World Production:
Constraints are discovered, not given:
- "Our database has 50 million user records."
- "The API must respond within 200ms."
- "We have 4 GB of memory available per container."
- "The batch job must complete in 4 hours."
- "We process 10,000 requests per second."
Production constraints often include:
- Concurrency: Multiple simultaneous requests share resources
- Durability: Operations must survive crashes
- Consistency: Distributed system constraints
- Cost: Compute/storage costs real money
The analytical skills transfer directly—you're still mapping resources to algorithm viability.
In production, err on the side of simplicity until proven otherwise. A maintainable O(n log n) algorithm that every engineer understands beats a clever O(n) algorithm that only one person can modify. Optimize when constraints demand it, not before.
Practice: Analyzing Constraints
Let's practice translating constraints into algorithm requirements. For each scenario, determine which algorithms are viable.
Exercise 1:
n ≤ 10⁵, time limit 1 second. Find pair summing to target.
- O(n²) brute force: 10¹⁰ operations ❌ (100x over budget)
- O(n log n) sort + two pointers: ~2 × 10⁶ ✓
- O(n) hash set: ~10⁵ ✓ (optimal)
Exercise 2:
n ≤ 3,000, time limit 2 seconds. Find longest increasing subsequence.
- O(n²) DP: 9 × 10⁶ operations ✓ (fits comfortably)
- O(n log n) binary search DP: ~5 × 10⁴ ✓ (overkill but works)
N is small enough that O(n²) is fine here!
Exercise 3:
n ≤ 20, find subset with maximum sum ≤ target.
- O(2ⁿ) enumerate all subsets: 2²⁰ ≈ 10⁶ ✓ (small n means exponential is viable)
- Meet in the middle: 2 × 2¹⁰ = 2 × 10³ ✓ (even better)
Exercise 4:
n × m grid where n, m ≤ 10,000. Find shortest path.
- Grid size: 10⁸ cells
- BFS is O(V + E) = O(n × m) = 10⁸ operations — borderline ⚠️
- Memory for grid: 10⁸ bytes = 100 MB — might be okay, check limit
- If n, m ≤ 1,000: 10⁶ cells, perfectly fine ✓
Exercise 5:
Q ≤ 10⁵ queries on array of n ≤ 10⁵ elements. Each query: range sum.
- O(n) per query: 10¹⁰ total ❌
- O(1) per query with prefix sums: O(n) preprocess + O(Q) queries = 2 × 10⁵ ✓
This constraint pattern (large n, large Q) signals: precompute!
Make constraint analysis your first step on EVERY problem. Before sketching an algorithm, calculate: 'If n = [max value], does O(whatever) fit?' This 10-second check prevents hours of debugging timeout errors.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.