If you've ever needed to verify if a list of stock prices, sensor readings, or ML training losses is trending consistently in one direction, checking for monotonicity is key. This is a common task in data pipelines, time-series analysis, and even tech interviews (shoutout to LeetCode 896!).
What Is a Monotonic Sequence?
A monotonic sequence is one that is entirely non-decreasing or entirely non-increasing.
It never changes direction — no zigzagging.
Main Types
Non-decreasing
Each element ≤ next element
Allows equals
Example:[1, 2, 2, 3, 5]Strictly increasing
Each element < next element
No equals allowed
Example:[1, 3, 4, 8]Non-increasing
Each element ≥ next element
Allows equals
Example:[9, 7, 7, 4, 2]Strictly decreasing
Each element > next element
No equals allowed
Example:[10, 8, 5, 1]
Most algorithm problems use non-strict (≤ or ≥) unless they explicitly say “strictly”.
Quick Comparison Table
| Sequence | Non-Decreasing | Strictly Increasing | Non-Increasing | Strictly Decreasing |
|---|---|---|---|---|
[1, 2, 2, 3] |
✓ | ✗ | ✗ | ✗ |
[1, 2, 3, 4] |
✓ | ✓ | ✗ | ✗ |
[5, 4, 4, 1] |
✗ | ✗ | ✓ | ✗ |
[5, 5, 5, 5] |
✓ | ✗ | ✓ | ✗ |
[] (empty) |
✓ | ✓ | ✓ | ✓ |
[42] (single) |
✓ | ✓ | ✓ | ✓ |
Important Edge Cases
- Empty list
[]→ monotonic (vacuously true) - Single element
[x]→ always monotonic - All equal
[7,7,7,7]→ both non-decreasing and non-increasing - Floating point values → dangerous for strict checks
0.1 + 0.2 == 0.3is false in most languages → Usemath.isclose()or small epsilon - Negative numbers → handled normally
- Mixed types (
[1, "2"]) → usually raises error
Floating-point strict check tip (Python example)
from math import isclose
def is_strictly_increasing_float(seq, rel_tol=1e-9, abs_tol=1e-12):
return all(x < y and not isclose(x, y, rel_tol=rel_tol, abs_tol=abs_tol) for x, y in zip(seq, seq[1:]))
7 Practical Methods to Check Monotonicity
All methods below check for non-strict monotonicity by default
→ sequence is either non-decreasing (≤) or non-increasing (≥)
Most production code and interviews expect this behavior unless “strictly” is specified.
1. Simple Loop with Flags (O(n) time, O(1) space)
Great for interviews — very explicit and easy to explain.
def is_monotonic(nums):
if len(nums) <= 1:
return True
increasing = decreasing = True
for i in range(len(nums) - 1):
if nums[i] > nums[i + 1]:
increasing = False
if nums[i] < nums[i + 1]:
decreasing = False
return increasing or decreasing
2. Using all() with Early Exit (O(n) time, O(1) space)
Pythonic, very readable, and now optimized with early exit so it stops as soon as it finds a violation — much better for large lists that are not monotonic.
Classic two-pass version (no early exit)
def is_monotonic(nums):
return (all(nums[i] <= nums[i+1] for i in range(len(nums)-1)) or
all(nums[i] >= nums[i+1] for i in range(len(nums)-1)))
3. zip() for Pairwise Comparison (O(n) time, ~O(n) space)
One of the cleanest and most Pythonic ways to check monotonicity.
We use zip(arr, arr[1:]) to create consecutive pairs without manually handling indices.
Basic version (two logical passes)
def is_monotonic(nums):
return (all(x <= y for x, y in zip(nums, nums[1:])) or
all(x >= y for x, y in zip(nums, nums[1:])))
4. sorted() Comparison (O(n log n) time, O(n) space)
This is one of the simplest and most intuitive ways to check monotonicity — especially useful when:
- You're prototyping quickly
- The input size is small (n ≤ ~10⁴–10⁵)
- You want the shortest, most obvious code possible
- You're explaining the concept to beginners
However, avoid this in production or in coding interviews when performance matters, because sorting is unnecessary work when we only need to check order.
Classic One-Liner Version
def is_monotonic(nums):
return nums == sorted(nums) or nums == sorted(nums, reverse=True)
5. NumPy for Data Pros (O(n) time, O(n) space)
Vectorized, extremely fast on large arrays, perfect when you're already working in a NumPy / pandas / data-science environment.
This is the go-to method in scientific computing, machine learning pipelines, time-series analysis, and any situation where your data is already a NumPy array (or easily convertible).
Core Implementation
import numpy as np
def is_monotonic(arr):
"""
Check if array is monotonic (non-decreasing or non-increasing) using NumPy.
Fast for large arrays thanks to vectorized operations.
"""
if len(arr) <= 1:
return True
# Convert to numpy array if it isn't already
arr = np.asarray(arr)
# Compute differences in one vectorized operation
diff = np.diff(arr)
# Check if all differences are non-negative OR all are non-positive
return np.all(diff >= 0) or np.all(diff <= 0)
6. itertools.pairwise() (Python 3.10+, O(n) time, O(1) space)
The modern, cleanest iterator-based approach — available since Python 3.10.
No list slicing, no manual indexing, no extra memory for pairs — pure lazy iteration.
This is many people's current go-to when writing new code targeting recent Python versions.
Basic Elegant Version
from itertools import pairwise
def is_monotonic(nums):
if len(nums) <= 1:
return True
return (all(x <= y for x, y in pairwise(nums)) or
all(x >= y for x, y in pairwise(nums)))
7. One-Pass Direction Detection (O(n) time, O(1) space)
This is widely considered the interview favorite and one of the cleanest production-ready solutions.
- Single pass through the array
- Constant extra space
- Early exit as soon as direction conflict is detected
- Gracefully handles leading equal elements (plateaus)
- Works for both non-strict increasing and decreasing
Classic & Optimized Version
def is_monotonic(nums):
if len(nums) <= 1:
return True
direction = 0 # 0 = unknown, 1 = increasing, -1 = decreasing
for i in range(len(nums) - 1):
if nums[i] < nums[i + 1]:
if direction == 0:
direction = 1
elif direction == -1:
return False
elif nums[i] > nums[i + 1]:
if direction == 0:
direction = -1
elif direction == 1:
return False
# else: equal → no change to direction (continue)
return True
Performance Breakdown
Here's a concise comparison of the 7 methods we covered, focusing on the key performance and practical aspects.
| Method | Time | Space | Early Exit? | Best For | Notes / Trade-offs |
|---|---|---|---|---|---|
| 1. Simple Loop + Flags | O(n) | O(1) | Yes | Interviews, debugging, explanation | Very explicit, easy to modify for strict mode |
| 2. all() / Two-Pass | O(n) | O(1) | Partial | Clean, readable code | Always full passes unless using for-loop break |
| 3. zip() Pairwise | O(n) | ~O(1) | Partial | Readability, Pythonic style | zip is lazy → low memory, but two passes common |
| 4. sorted() Comparison | O(n log n) | O(n) | No | Quick prototyping, teaching | ~30× slower on large data — avoid in prod |
| 5. NumPy diff | O(n) | O(n) | No | Large arrays, data science | Fastest on big numeric data (vectorized) |
| 6. itertools.pairwise() | O(n) | O(1) | Partial | Modern Python (3.10+), clean code | Iterator magic — zero-copy pairs |
| 7. Direction Detection | O(n) | O(1) | Yes | Advanced interviews, production | Single pass + early exit = best real-world perf |
Real-World Benchmarks (approx. on 1 million elements, Python 3.11–3.12, typical laptop)
- Pure O(n) methods (loops, zip, pairwise, direction detection): ~20–35 ms
- NumPy diff + all(): ~8–15 ms (often fastest due to vectorization)
- sorted() comparison: ~700–1000 ms (~30–50× slower)
Early exit methods (especially #1 Flags, #7 Direction Detection, and single-pass violation checks) shine on messy / non-monotonic data:
- Zigzag early → return in < 1 ms
- Mostly monotonic but fails late → still full pass, but average case much better than always-full-pass methods
Quick Decision Guide
| Scenario | Recommended Method(s) |
|---|---|
| Coding interview | #7 Direction Detection or #1 Flags |
| Clean & short production code | #6 pairwise() or #2 all() |
| Already using NumPy / pandas | #5 NumPy diff |
| Python 3.10+ and want modern style | #6 itertools.pairwise |
| Quick script / notebook / teaching | #4 sorted() (then optimize if needed) |
| Need strict monotonicity | Modify #7 or #6 with < / > checks |
| Float data with precision issues | Add tolerances (#5 NumPy best) |
| Very large arrays (>10M elements) | #5 NumPy or #7 with early exit |
Bottom line
→ Method 7 (Direction Detection) or Method 6 (pairwise + violation check) is the sweet spot for general-purpose, interview-ready, production-grade code.
→ Switch to NumPy the moment you're dealing with serious numerical data volumes.
Interview Hacks & Common Pitfalls
Clarify strictness: Ask if equals are allowed (non-strict ≤/≥ vs strict < />).
Streaming adaptation: Use a single
prevvariable to handle generators / online data.Avoid sorted(): Interviewers want O(n) time — mention it only to show understanding, then pivot to linear solution.
-
Pitfalls to avoid:
- Wrong loop range → IndexError (use
range(1, len(arr))orzip/pairwise) - Ignoring / mishandling equal elements
- Not handling edge cases: empty list
[], single element[x], all equal[5,5,5] - Floating-point precision issues (direct comparisons fail)
- Assuming numeric input only (mixed types crash)
- Wrong loop range → IndexError (use
Real-world examples:
- Stock price trends: non-decreasing prices over time?
- Machine learning: validation / training loss monotonically decreasing?
- Timestamp / event log validation: events in non-decreasing order?
- Sensor data checks: readings steadily increasing or decreasing?
For the full deep dive (including configurable strict mode and more code), check the original on emiTechLogic: https://emitechlogic.com/monotonic-sequence-in-python/
What's your favorite method?
Ever hit a monotonic bug in production?
Drop a comment below — I'd love to hear!
Want to Read More?
If you enjoyed this deep dive into checking monotonic sequences in Python, here are some related articles from emiTechLogic that build on similar concepts:
- Check if a List is Sorted in Python — Learn multiple ways to verify if a list is already sorted (closely related to monotonic checks, since a sorted list is monotonic by definition).
- Check if a Tuple is Sorted in Python — Extend the idea to immutable sequences like tuples.
-
Sorting Algorithms in Python — Understand why we avoid
sorted()for monotonic checks and explore actual sorting implementations. - Python Tuples — Dive deeper into tuples, which often appear in pairwise/monotonic-related problems.
- Top 10 Python Interview Questions — More common interview problems like this one (including sequence and data structure questions).
- Python Optimization Guide: How to Write Faster, Smarter Code — Tips on performance that complement our discussion of O(n) vs O(n log n) approaches.
For the full collection of Python tutorials, guides, and interview prep, visit the emiTechLogic Blog.
Top comments (0)