Home DSA Convex Hull: Graham Scan Algorithm Explained with Code
Intermediate 4 min · July 14, 2026

Convex Hull: Graham Scan Algorithm Explained with Code

Learn the Graham Scan algorithm for computing the convex hull of a set of points.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 20-25 min read
  • Basic understanding of arrays and sorting
  • Familiarity with stack data structure
  • Knowledge of cross product and geometry basics
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Convex hull is the smallest convex polygon containing all points.
  • Graham Scan sorts points by polar angle and uses a stack to build the hull.
  • Time complexity: O(n log n) due to sorting.
  • Handles collinear points by keeping only the farthest.
  • Common in collision detection, image processing, and GIS.
✦ Definition~90s read
What is Convex Hull?

Graham Scan is an algorithm that computes the convex hull of a set of points in O(n log n) time by sorting points by polar angle and using a stack to build the hull.

Imagine you have a bunch of nails hammered into a board.
Plain-English First

Imagine you have a bunch of nails hammered into a board. The convex hull is like stretching a rubber band around all the nails; the band touches only the outermost nails and forms a convex shape. Graham Scan is a way to find which nails the band touches, in order, by starting from the lowest nail and then scanning around in order of angle.

Have you ever needed to find the boundary of a set of points? Whether you're building a game with collision detection, analyzing geographic data, or simplifying shapes in computer graphics, the convex hull is a fundamental concept. The convex hull of a set of points is the smallest convex polygon that contains all the points. Think of it as the shape formed by a rubber band stretched around the outermost points.

Among the many algorithms to compute the convex hull, the Graham Scan is one of the most elegant and efficient. It runs in O(n log n) time, thanks to an initial sorting step, and uses a simple stack-based approach to build the hull. In this tutorial, you'll learn how the Graham Scan works, step by step, with clear Python code. We'll also cover common pitfalls, debugging strategies, and real-world applications. By the end, you'll be able to implement the algorithm confidently and understand when to use it.

What is the Convex Hull?

The convex hull of a set of points is the smallest convex polygon that contains all the points. In other words, it's the shape you get if you wrap a rubber band around the outermost points. The convex hull is a fundamental concept in computational geometry with applications in pattern recognition, image processing, statistics (e.g., the convex hull of a data set), and game development (e.g., collision detection).

Formally, a set S is convex if for any two points in S, the line segment connecting them lies entirely in S. The convex hull of a set of points P is the intersection of all convex sets containing P. For a finite set of points, the convex hull is a convex polygon whose vertices are a subset of the original points.

convex_hull_intro.pyPYTHON
1
2
3
# Example: Convex hull of a set of points
points = [(0,0), (1,1), (2,2), (2,0), (1,0), (0,2)]
# The convex hull would be [(0,0), (2,0), (2,2), (0,2)]
🔥Why Convex Hull?
📊 Production Insight
In production, you may need to handle degenerate cases like all points collinear or duplicate points. Always validate input.
🎯 Key Takeaway
The convex hull is the minimal convex polygon enclosing all points.
convex-hull-graham-scan THECODEFORGE.IO Graham Scan Algorithm Steps Step-by-step process for computing convex hull Find Lowest Point Select point with minimum y-coordinate Sort by Polar Angle Sort remaining points by angle relative to lowest Initialize Stack Push first three sorted points onto stack Process Each Point For each point, check orientation of top two stack points Pop Non-Left Turns While orientation is not counterclockwise, pop stack Push Current Point Add current point to stack after valid turns ⚠ Collinear points cause stack errors Use orientation function to skip or handle collinear points THECODEFORGE.IO
thecodeforge.io
Convex Hull Graham Scan

Graham Scan Algorithm Overview

The Graham Scan algorithm computes the convex hull of a set of points in O(n log n) time. It works in three main steps:

  1. Find the point with the lowest y-coordinate (and leftmost if ties). This point is guaranteed to be on the hull.
  2. Sort all other points by polar angle relative to this base point. If two points have the same angle, keep only the farthest one (since the closer one is inside the hull edge).
  3. Build the hull using a stack: Iterate through the sorted points. For each point, while the last two points on the stack and the new point make a non-left turn (i.e., clockwise or collinear), pop the stack. Then push the new point.

The algorithm uses the orientation test (cross product) to determine the turn direction. A left turn means the point is part of the hull; a right turn means the previous point is not on the hull.

graham_scan_overview.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def orientation(p, q, r):
    # Returns positive if counter-clockwise, negative if clockwise, 0 if collinear
    return (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0])

def graham_scan(points):
    # Step 1: Find lowest point (and leftmost)
    start = min(points, key=lambda p: (p[1], p[0]))
    # Step 2: Sort by polar angle
    sorted_pts = sorted(points, key=lambda p: (math.atan2(p[1]-start[1], p[0]-start[0]), p))
    # Step 3: Build hull
    stack = []
    for p in sorted_pts:
        while len(stack) >= 2 and orientation(stack[-2], stack[-1], p) <= 0:
            stack.pop()
        stack.append(p)
    return stack
💡Sorting by Polar Angle
📊 Production Insight
Sorting by angle using atan2 can be slow for large datasets. Consider using a custom comparator that uses cross product for better performance.
🎯 Key Takeaway
Graham Scan sorts points by angle and uses a stack to maintain the hull.

Implementing the Orientation Function

The orientation function is the heart of the Graham Scan. It determines whether three points make a left turn, right turn, or are collinear. Given points p, q, r, the cross product (q - p) × (r - p) gives: - Positive: counter-clockwise (left turn) - Negative: clockwise (right turn) - Zero: collinear

``python def orientation(p, q, r): return (q[0] - p[0]) (r[1] - p[1]) - (q[1] - p[1]) (r[0] - p[0]) ``

This function is used in the while loop to pop points that are not part of the convex hull. When orientation is <= 0, we pop because we want to keep only left turns (counter-clockwise). If we want to include collinear points on the hull, we can change the condition to < 0 (strict left turn) and handle collinear points separately.

orientation.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
def orientation(p, q, r):
    val = (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0])
    if val == 0:
        return 0  # collinear
    return 1 if val > 0 else -1  # 1: ccw, -1: cw

# Example
p = (0,0)
q = (1,1)
r = (2,2)
print(orientation(p, q, r))  # 0 (collinear)
Output
0
⚠ Floating-Point Precision
📊 Production Insight
Always use an epsilon tolerance when comparing orientation to zero to avoid errors from floating-point arithmetic.
🎯 Key Takeaway
Orientation uses cross product to determine turn direction.
convex-hull-graham-scan THECODEFORGE.IO Convex Hull Computation Stack Layered components from input to output Input Layer Point Set | Coordinate Pairs Preprocessing Lowest Point Finder | Polar Angle Sorter Core Algorithm Orientation Function | Stack Manager | Collinear Handler Output Layer Convex Hull Points | Boundary Edges THECODEFORGE.IO
thecodeforge.io
Convex Hull Graham Scan

Handling Collinear Points

Collinear points on the hull boundary are tricky. If multiple points lie on the same line segment that is part of the hull, we only want the endpoints. The standard Graham Scan pops points when orientation <= 0, which removes collinear points. However, if we want to include all collinear points on the hull (e.g., for a more detailed boundary), we need to adjust.

One approach: When sorting, if two points have the same angle relative to the start, keep only the farthest one. Then, during the stack building, use orientation < 0 (strict left turn) to pop, and handle collinear points by keeping them if they are farther from the start. Alternatively, you can first compute the hull with strict left turns, then add collinear points that lie on the hull edges.

In practice, for most applications, you want the minimal convex hull, so you can safely remove collinear points. But if you need all boundary points, you must modify the algorithm.

collinear_handling.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import math

def graham_scan_collinear(points):
    start = min(points, key=lambda p: (p[1], p[0]))
    # Sort by angle, and for same angle, by distance (farther first)
    def angle_key(p):
        return (math.atan2(p[1]-start[1], p[0]-start[0]), 
                (p[0]-start[0])**2 + (p[1]-start[1])**2)
    sorted_pts = sorted(points, key=angle_key)
    # Remove duplicates with same angle (keep farthest)
    unique = []
    for p in sorted_pts:
        if unique and orientation(start, unique[-1], p) == 0:
            # Same angle: replace if farther
            if (p[0]-start[0])**2 + (p[1]-start[1])**2 > (unique[-1][0]-start[0])**2 + (unique[-1][1]-start[1])**2:
                unique[-1] = p
        else:
            unique.append(p)
    # Build hull with strict left turn (orientation > 0)
    stack = []
    for p in unique:
        while len(stack) >= 2 and orientation(stack[-2], stack[-1], p) <= 0:
            stack.pop()
        stack.append(p)
    return stack
💡Collinearity in Production
📊 Production Insight
If you need to preserve collinear boundary points, consider a post-processing step that adds them back after computing the minimal hull.
🎯 Key Takeaway
Collinear points on the hull can be handled by keeping only the farthest when sorting by angle.

Complete Python Implementation

Here is a complete, production-ready implementation of the Graham Scan algorithm in Python. It includes error handling for edge cases and uses an epsilon for floating-point comparisons.

```python import math

def orientation(p, q, r, eps=1e-9): val = (q[0] - p[0]) (r[1] - p[1]) - (q[1] - p[1]) (r[0] - p[0]) if abs(val) < eps: return 0 return 1 if val > 0 else -1

def graham_scan(points): if len(points) < 3: return points[:] # Not enough points for a polygon # Find the point with the lowest y (and leftmost if tie) start = min(points, key=lambda p: (p[1], p[0])) # Sort by polar angle and distance def key_func(p): angle = math.atan2(p[1] - start[1], p[0] - start[0]) dist = (p[0] - start[0])2 + (p[1] - start[1])2 return (angle, dist) sorted_pts = sorted(points, key=key_func) # Remove duplicates of same angle (keep farthest) unique = [] for p in sorted_pts: if unique and orientation(start, unique[-1], p) == 0: # Same angle: keep the one farther from start if (p[0]-start[0])2 + (p[1]-start[1])2 > (unique[-1][0]-start[0])2 + (unique[-1][1]-start[1])2: unique[-1] = p else: unique.append(p) # Build hull using stack stack = [] for p in unique: while len(stack) >= 2 and orientation(stack[-2], stack[-1], p) <= 0: stack.pop() stack.append(p) return stack ```

This implementation returns the convex hull points in counter-clockwise order, starting from the lowest point.

graham_scan_complete.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import math

def orientation(p, q, r, eps=1e-9):
    val = (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0])
    if abs(val) < eps:
        return 0
    return 1 if val > 0 else -1

def graham_scan(points):
    if len(points) < 3:
        return points[:]
    start = min(points, key=lambda p: (p[1], p[0]))
    def key_func(p):
        angle = math.atan2(p[1] - start[1], p[0] - start[0])
        dist = (p[0] - start[0])**2 + (p[1] - start[1])**2
        return (angle, dist)
    sorted_pts = sorted(points, key=key_func)
    unique = []
    for p in sorted_pts:
        if unique and orientation(start, unique[-1], p) == 0:
            if (p[0]-start[0])**2 + (p[1]-start[1])**2 > (unique[-1][0]-start[0])**2 + (unique[-1][1]-start[1])**2:
                unique[-1] = p
        else:
            unique.append(p)
    stack = []
    for p in unique:
        while len(stack) >= 2 and orientation(stack[-2], stack[-1], p) <= 0:
            stack.pop()
        stack.append(p)
    return stack

# Example usage
points = [(0,0), (1,1), (2,2), (2,0), (1,0), (0,2)]
hull = graham_scan(points)
print(hull)
Output
[(0, 0), (2, 0), (2, 2), (0, 2)]
🔥Complexity Analysis
📊 Production Insight
In production, consider using a custom comparator for sorting to avoid atan2 overhead. Also, handle large datasets by using an efficient sorting algorithm.
🎯 Key Takeaway
The complete implementation handles edge cases and returns the convex hull in counter-clockwise order.

Testing and Validation

To ensure your Graham Scan implementation is correct, test with various cases:

  1. Simple square: Points at (0,0), (0,1), (1,1), (1,0) should return all four.
  2. Triangle with interior point: Points (0,0), (1,0), (0,1), (0.2,0.2) should return the triangle.
  3. Collinear points: Points (0,0), (1,1), (2,2), (3,3) should return the endpoints (0,0) and (3,3).
  4. Duplicate points: Should be handled gracefully (e.g., by removing duplicates first).
  5. All points collinear: Should return the two farthest points.
  6. Single point: Should return that point.
  7. Two points: Should return both.

You can also validate by checking that all original points are inside or on the hull. Use a point-in-polygon test for verification.

test_graham_scan.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def test_graham_scan():
    # Test 1: Square
    points = [(0,0), (0,1), (1,1), (1,0)]
    hull = graham_scan(points)
    assert len(hull) == 4
    # Test 2: Triangle with interior
    points = [(0,0), (1,0), (0,1), (0.2,0.2)]
    hull = graham_scan(points)
    assert len(hull) == 3
    # Test 3: Collinear
    points = [(0,0), (1,1), (2,2), (3,3)]
    hull = graham_scan(points)
    assert len(hull) == 2
    # Test 4: Single point
    points = [(1,1)]
    hull = graham_scan(points)
    assert hull == [(1,1)]
    print("All tests passed!")

test_graham_scan()
Output
All tests passed!
💡Automated Testing
📊 Production Insight
For production, consider using a library like scipy.spatial.ConvexHull for robustness, but understanding the algorithm helps you debug when things go wrong.
🎯 Key Takeaway
Thorough testing with edge cases ensures algorithm correctness.
Graham Scan vs Jarvis March Comparison of two convex hull algorithms Graham Scan Jarvis March Time Complexity O(n log n) O(nh) Space Complexity O(n) O(n) Sorting Required Yes, polar angle sort No sorting needed Handles Collinear Points Requires special handling Naturally handles them Best For Large point sets Small hull size (h) THECODEFORGE.IO
thecodeforge.io
Convex Hull Graham Scan

Real-World Applications

  • Collision Detection: In games and robotics, convex hulls approximate object shapes for fast collision checks.
  • Image Processing: Hand gesture recognition uses convex hull of hand contour to detect fingers.
  • Geographic Information Systems (GIS): Computing the boundary of a set of points (e.g., city limits).
  • Statistics: The convex hull of a dataset can be used to identify outliers.
  • Pattern Recognition: In machine learning, convex hulls help in support vector machines (SVMs) for finding support vectors.

For example, in a game, you might compute the convex hull of a player's character to detect collisions with obstacles. The hull simplifies the character's shape into a convex polygon, making collision checks faster.

application_example.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Example: Collision detection using convex hull
# Assume we have two convex hulls (list of points)
def check_collision(hull1, hull2):
    # Simple check: if any point of one hull is inside the other
    for point in hull1:
        if point_in_convex_polygon(point, hull2):
            return True
    for point in hull2:
        if point_in_convex_polygon(point, hull1):
            return True
    return False

# point_in_convex_polygon uses orientation to check if point is inside
# (implementation omitted for brevity)
🔥Performance Considerations
📊 Production Insight
When integrating convex hull into a production system, profile the algorithm with your actual data size. For very large datasets, consider using a library or parallelizing the sorting step.
🎯 Key Takeaway
Convex hull is a versatile tool in computational geometry with many practical uses.
● Production incidentPOST-MORTEMseverity: high

The Case of the Missing Polygon: A GIS Outage

Symptom
Users reported that some city outlines on the map had missing sections, appearing as if parts of the city were cut out.
Assumption
The developer assumed the convex hull algorithm was correct because it worked on test data with no collinear points.
Root cause
The Graham Scan implementation did not handle collinear points on the hull boundary. When multiple points lay on the same line, the algorithm incorrectly removed points that should have been part of the hull.
Fix
Modified the orientation check to keep the farthest point when points are collinear, ensuring the hull includes all boundary points.
Key lesson
  • Always test with collinear points, especially on the hull boundary.
  • Use robust orientation functions that handle floating-point precision.
  • Validate output by checking that all points are inside or on the hull.
  • Consider edge cases like duplicate points or all points collinear.
  • Write unit tests that include real-world data with noise.
Production debug guideSymptom to Action4 entries
Symptom · 01
Hull polygon is self-intersecting or has spikes.
Fix
Check orientation function for floating-point errors. Use a small epsilon tolerance.
Symptom · 02
Some points are outside the computed hull.
Fix
Verify that all points are considered; ensure sorting by polar angle is correct and that the stack algorithm includes all necessary points.
Symptom · 03
Hull is missing points that are clearly on the boundary.
Fix
Check handling of collinear points. Ensure that when three points are collinear, the middle one is skipped only if it is not on the hull edge.
Symptom · 04
Algorithm crashes or returns empty hull for small input.
Fix
Handle base cases: fewer than 3 points return all points (or empty if none). Ensure at least one point exists.
★ Quick Debug Cheat SheetCommon issues and immediate fixes for Graham Scan.
Self-intersecting hull
Immediate action
Check orientation function
Commands
print(orientation(p, q, r))
Check if epsilon is used
Fix now
Use a small epsilon (e.g., 1e-9) in orientation comparison.
Missing boundary points+
Immediate action
Check collinearity handling
Commands
print(sorted_points)
Check stack after each pop
Fix now
When collinear, keep the farthest point from the start.
Empty hull for valid points+
Immediate action
Check base case
Commands
print(len(points))
Check if points are all same
Fix now
If n < 3, return points (or [] if none).
Wrong order of hull points+
Immediate action
Check sorting
Commands
print(sorted_points)
Check comparator
Fix now
Sort by polar angle relative to the lowest point; use cross product as comparator.
AlgorithmTime ComplexitySpace ComplexityHandles CollinearImplementation Difficulty
Graham ScanO(n log n)O(n)Yes (with care)Medium
Jarvis MarchO(nh)O(n)YesEasy
Divide and ConquerO(n log n)O(n)YesHard
QuickhullO(n log n) averageO(n)YesMedium
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
convex_hull_intro.pypoints = [(0,0), (1,1), (2,2), (2,0), (1,0), (0,2)]What is the Convex Hull?
graham_scan_overview.pydef orientation(p, q, r):Graham Scan Algorithm Overview
orientation.pydef orientation(p, q, r):Implementing the Orientation Function
collinear_handling.pydef graham_scan_collinear(points):Handling Collinear Points
graham_scan_complete.pydef orientation(p, q, r, eps=1e-9):Complete Python Implementation
test_graham_scan.pydef test_graham_scan():Testing and Validation
application_example.pydef check_collision(hull1, hull2):Real-World Applications

Key takeaways

1
Graham Scan computes the convex hull in O(n log n) time using sorting and a stack.
2
The orientation test is fundamental; use an epsilon for floating-point safety.
3
Handle edge cases
collinear points, duplicates, and small point sets.
4
Test thoroughly with various inputs to ensure correctness.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the Graham Scan algorithm for computing the convex hull.
Q02SENIOR
How would you modify Graham Scan to include all collinear points on the ...
Q03JUNIOR
What is the orientation test and why is it important?
Q04SENIOR
Compare Graham Scan with the Divide and Conquer approach for convex hull...
Q01 of 04SENIOR

Explain the Graham Scan algorithm for computing the convex hull.

ANSWER
Graham Scan finds the convex hull by first selecting the point with the lowest y-coordinate (and leftmost if tie). Then it sorts all other points by polar angle relative to that point. Finally, it uses a stack to build the hull: for each point in sorted order, it pops points from the stack that make a non-left turn (clockwise or collinear) with the new point, then pushes the new point.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the time complexity of Graham Scan?
02
How does Graham Scan handle collinear points?
03
Can Graham Scan be used for 3D points?
04
What is the difference between Graham Scan and Jarvis March?
05
How do I handle duplicate points in Graham Scan?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Geometry. Mark it forged?

4 min read · try the examples if you haven't

Previous
Rotating Calipers — Diameter and Width
5 / 7 · Geometry
Next
Closest Pair of Points: Divide-and-Conquer Algorithm in O(n log n)