Off-Ball Movement and Screens
Off-Ball Movement in Basketball
Introduction
Off-ball movement is one of the most critical yet underappreciated aspects of basketball. While ball-handlers get most of the attention, elite off-ball players create scoring opportunities through intelligent cuts, screens, and relocations. Modern analytics and tracking data have revealed the enormous impact of off-ball movement on offensive efficiency, spacing, and defensive breakdowns.
This analysis explores the types of off-ball movement, tracking methodologies, player evaluations, and team systems that emphasize constant motion.
Types of Off-Ball Movement
1. Cutting
Backdoor Cuts: When a defender overplays, the offensive player cuts behind them toward the basket for an easy layup. Requires excellent timing and court awareness.
V-Cuts: Player moves away from desired position, then sharply cuts back to create separation from defender. Common for getting open on the perimeter.
L-Cuts: Two-directional cut forming an "L" shape, often used to get open off screens or in post-up situations.
Basket Cuts: Direct cuts to the rim through gaps in the defense, exploiting help-side rotations or defensive lapses.
Flare Cuts: Cutting away from the ball toward the perimeter, often used with screens to create three-point opportunities.
2. Screening Actions
Off-Ball Screens: Setting screens away from the ball to free up teammates, including down screens, back screens, and cross screens.
Screen the Screener: Advanced action where the initial screener receives a screen immediately after setting one, creating confusion in defensive rotations.
Slip Screens: Releasing from a screen early to cut to the basket when the defender anticipates the screen.
Re-Screens: Setting multiple consecutive screens to wear down defenders and create better separation.
3. Relocations and Spacing
Spot-Up Relocations: Moving to open areas on the perimeter after initial actions, maintaining floor spacing and shot readiness.
Drift Actions: Gradually moving to better positions while the ball is being worked, staying ready without standing still.
Lift Movements: Coming up from the baseline or corners to the wings, creating passing angles and spacing.
Corner Fills: Rotating to corner positions as actions develop, maintaining three-point threat spacing.
Tracking Data for Off-Ball Activity
Key Metrics
- Distance Traveled: Total distance covered without the ball, indicating activity level and conditioning
- Average Speed: Speed while moving off-ball, showing urgency and purpose of movement
- Touches After Movement: Possessions received following cuts or relocations
- Points Per Touch After Movement: Scoring efficiency on possessions following off-ball actions
- Screen Assists: Points generated by teammates after setting screens
- Gravity Score: Defensive attention drawn away from the ball, measured by defender proximity and help rotations
- Spacing Index: Contribution to floor spacing based on positioning and shooting threat
Advanced Tracking Approaches
SportVU/Second Spectrum Data: Optical tracking systems capture player coordinates 25 times per second, enabling precise movement analysis.
Synergy Sports: Play-type tracking categorizes possessions by action type (cuts, screens, spot-ups), allowing detailed off-ball evaluation.
STATS Perform: Event-level data on screens set, cuts made, and defensive attention drawn.
Python Code for Movement Analysis
import pandas as pd
import numpy as np
from scipy.spatial.distance import euclidean
import matplotlib.pyplot as plt
import seaborn as sns
class OffBallMovementAnalyzer:
"""
Analyze off-ball movement patterns using tracking data
"""
def __init__(self, tracking_data):
"""
Initialize with tracking data containing player coordinates
Parameters:
-----------
tracking_data : pd.DataFrame
Columns: game_id, play_id, frame, player_id, x, y, has_ball
"""
self.data = tracking_data
def calculate_distance_traveled(self, player_id, play_id=None):
"""
Calculate total distance traveled by player while not holding ball
"""
df = self.data[self.data['player_id'] == player_id].copy()
if play_id:
df = df[df['play_id'] == play_id]
# Filter for frames where player doesn't have ball
df = df[df['has_ball'] == False].sort_values(['play_id', 'frame'])
# Calculate distance between consecutive frames
df['distance'] = np.sqrt(
(df['x'].diff())**2 + (df['y'].diff())**2
)
# Reset distance at play boundaries
df.loc[df['play_id'] != df['play_id'].shift(), 'distance'] = 0
return df['distance'].sum()
def calculate_movement_speed(self, player_id, window_frames=25):
"""
Calculate average movement speed (feet per second)
NBA tracking data: 25 frames per second
"""
df = self.data[
(self.data['player_id'] == player_id) &
(self.data['has_ball'] == False)
].copy()
df = df.sort_values(['play_id', 'frame'])
# Calculate instantaneous speed
df['distance'] = np.sqrt(
(df['x'].diff())**2 + (df['y'].diff())**2
)
df.loc[df['play_id'] != df['play_id'].shift(), 'distance'] = 0
# Speed in feet per second (25 fps)
df['speed'] = df['distance'] * 25
# Calculate rolling average
df['avg_speed'] = df['speed'].rolling(
window=window_frames,
min_periods=1
).mean()
return df['avg_speed'].mean()
def identify_cuts(self, player_id, speed_threshold=10,
direction_to_basket=True):
"""
Identify cutting actions based on speed and direction
Parameters:
-----------
speed_threshold : float
Minimum speed (ft/s) to classify as cutting
direction_to_basket : bool
If True, only count movements toward basket
"""
df = self.data[
(self.data['player_id'] == player_id) &
(self.data['has_ball'] == False)
].copy()
df = df.sort_values(['play_id', 'frame'])
# Calculate speed
df['dx'] = df['x'].diff()
df['dy'] = df['y'].diff()
df['speed'] = np.sqrt(df['dx']**2 + df['dy']**2) * 25
# Basket is at (5.25, 25) for one end, (88.75, 25) for other
df['dist_to_near_basket'] = df.apply(
lambda row: min(
euclidean([row['x'], row['y']], [5.25, 25]),
euclidean([row['x'], row['y']], [88.75, 25])
), axis=1
)
# Check if moving toward basket
df['toward_basket'] = (
df['dist_to_near_basket'] <
df['dist_to_near_basket'].shift(1)
)
# Identify cuts
cuts = df[df['speed'] >= speed_threshold].copy()
if direction_to_basket:
cuts = cuts[cuts['toward_basket'] == True]
return cuts
def calculate_gravity_score(self, player_id, defender_data):
"""
Calculate defensive gravity - how much defensive attention
player draws away from ball
Parameters:
-----------
defender_data : pd.DataFrame
Tracking data for defenders with columns: frame, defender_id, x, y
"""
player_df = self.data[
(self.data['player_id'] == player_id) &
(self.data['has_ball'] == False)
].copy()
gravity_scores = []
for idx, row in player_df.iterrows():
frame = row['frame']
play_id = row['play_id']
# Get defender positions at this frame
defenders = defender_data[
(defender_data['frame'] == frame) &
(defender_data['play_id'] == play_id)
]
# Calculate distances from player to all defenders
distances = defenders.apply(
lambda d: euclidean(
[row['x'], row['y']],
[d['x'], d['y']]
), axis=1
)
# Gravity: number of defenders within 10 feet
close_defenders = (distances <= 10).sum()
# Weight by inverse distance (closer = more gravity)
gravity = (1 / (distances + 1)).sum()
gravity_scores.append({
'frame': frame,
'close_defenders': close_defenders,
'gravity': gravity
})
gravity_df = pd.DataFrame(gravity_scores)
return gravity_df['gravity'].mean()
def analyze_screen_effectiveness(self, player_id, screen_data):
"""
Analyze effectiveness of screens set by player
Parameters:
-----------
screen_data : pd.DataFrame
Screen events with columns: screen_id, screener_id,
recipient_id, result (made/miss/assist)
"""
screens = screen_data[screen_data['screener_id'] == player_id]
total_screens = len(screens)
screen_assists = (screens['result'] == 'assist').sum()
points_generated = screens[
screens['result'].isin(['made', 'assist'])
]['points'].sum()
return {
'total_screens': total_screens,
'screen_assists': screen_assists,
'points_per_screen': points_generated / total_screens if total_screens > 0 else 0,
'screen_assist_rate': screen_assists / total_screens if total_screens > 0 else 0
}
def plot_movement_heatmap(self, player_id, player_name):
"""
Create heatmap of player's off-ball positions
"""
df = self.data[
(self.data['player_id'] == player_id) &
(self.data['has_ball'] == False)
]
plt.figure(figsize=(12, 8))
# Create 2D histogram
plt.hexbin(df['x'], df['y'], gridsize=30, cmap='YlOrRd',
mincnt=1, alpha=0.8)
plt.colorbar(label='Time Spent (frames)')
plt.title(f'{player_name} - Off-Ball Movement Heatmap',
fontsize=16, fontweight='bold')
plt.xlabel('Court X Position (feet)')
plt.ylabel('Court Y Position (feet)')
# Add court markings
plt.axvline(x=47, color='gray', linestyle='--', alpha=0.5,
label='Half Court')
plt.xlim(0, 94)
plt.ylim(0, 50)
plt.tight_layout()
plt.show()
def compare_player_movement(self, player_ids, player_names):
"""
Compare off-ball movement metrics for multiple players
"""
metrics = []
for player_id, name in zip(player_ids, player_names):
distance = self.calculate_distance_traveled(player_id)
speed = self.calculate_movement_speed(player_id)
cuts = len(self.identify_cuts(player_id))
metrics.append({
'Player': name,
'Distance (ft)': distance,
'Avg Speed (ft/s)': speed,
'Cuts': cuts
})
df = pd.DataFrame(metrics)
# Create comparison plots
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
metrics_to_plot = ['Distance (ft)', 'Avg Speed (ft/s)', 'Cuts']
for idx, metric in enumerate(metrics_to_plot):
axes[idx].bar(df['Player'], df[metric], color='steelblue')
axes[idx].set_title(metric, fontweight='bold')
axes[idx].set_ylabel(metric)
axes[idx].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
return df
# Example Usage
if __name__ == "__main__":
# Load tracking data
tracking_data = pd.read_csv('nba_tracking_data.csv')
# Initialize analyzer
analyzer = OffBallMovementAnalyzer(tracking_data)
# Analyze Stephen Curry's off-ball movement
curry_distance = analyzer.calculate_distance_traveled(
player_id=201939 # Curry's player ID
)
curry_speed = analyzer.calculate_movement_speed(player_id=201939)
print(f"Stephen Curry Off-Ball Stats:")
print(f"Distance Traveled: {curry_distance:.1f} feet")
print(f"Average Speed: {curry_speed:.2f} ft/s")
# Identify cuts
cuts = analyzer.identify_cuts(player_id=201939, speed_threshold=10)
print(f"Cutting Actions: {len(cuts)}")
# Plot movement heatmap
analyzer.plot_movement_heatmap(201939, "Stephen Curry")
# Compare multiple players
players = [201939, 202691, 203081] # Curry, Klay, Beal
names = ["Stephen Curry", "Klay Thompson", "Bradley Beal"]
comparison = analyzer.compare_player_movement(players, names)
print("\nPlayer Comparison:")
print(comparison)
R Code for Spatial Analysis
# Off-Ball Movement Spatial Analysis in R
library(tidyverse)
library(ggplot2)
library(gganimate)
library(spatstat)
library(cluster)
# Load tracking data
load_tracking_data <- function(filepath) {
data <- read_csv(filepath)
return(data)
}
# Calculate movement vectors and patterns
analyze_movement_vectors <- function(tracking_data, player_id) {
player_data <- tracking_data %>%
filter(player_id == !!player_id, has_ball == FALSE) %>%
arrange(play_id, frame)
# Calculate movement vectors
player_data <- player_data %>%
group_by(play_id) %>%
mutate(
dx = x - lag(x, default = first(x)),
dy = y - lag(y, default = first(y)),
distance = sqrt(dx^2 + dy^2),
angle = atan2(dy, dx) * 180 / pi,
speed = distance * 25 # 25 fps
) %>%
ungroup()
return(player_data)
}
# Identify movement clusters (common positions)
cluster_movement_positions <- function(tracking_data, player_id, n_clusters = 5) {
player_positions <- tracking_data %>%
filter(player_id == !!player_id, has_ball == FALSE) %>%
select(x, y)
# K-means clustering
set.seed(123)
clusters <- kmeans(player_positions, centers = n_clusters)
# Add cluster assignments
player_positions$cluster <- clusters$cluster
# Calculate cluster centers
cluster_centers <- as.data.frame(clusters$centers)
cluster_centers$cluster <- 1:n_clusters
cluster_centers$frequency <- table(clusters$cluster)
return(list(
positions = player_positions,
centers = cluster_centers
))
}
# Analyze spatial distribution
spatial_distribution_analysis <- function(tracking_data, player_id) {
player_data <- tracking_data %>%
filter(player_id == !!player_id, has_ball == FALSE)
# Create point pattern
court_window <- owin(xrange = c(0, 94), yrange = c(0, 50))
pp <- ppp(player_data$x, player_data$y, window = court_window)
# Calculate density
density_map <- density(pp, sigma = 3)
# Calculate spatial statistics
spatial_stats <- list(
mean_x = mean(player_data$x),
mean_y = mean(player_data$y),
sd_x = sd(player_data$x),
sd_y = sd(player_data$y),
density = density_map
)
return(spatial_stats)
}
# Screen location analysis
analyze_screen_locations <- function(screen_data, player_id) {
screens <- screen_data %>%
filter(screener_id == player_id)
# Categorize screen locations
screens <- screens %>%
mutate(
zone = case_when(
x < 20 ~ "Paint",
x >= 20 & x < 30 ~ "Elbow",
x >= 30 & x < 50 ~ "Wing",
x >= 50 ~ "Beyond Half Court"
),
side = ifelse(y < 25, "Left", "Right")
)
# Calculate effectiveness by location
screen_effectiveness <- screens %>%
group_by(zone, side) %>%
summarise(
total_screens = n(),
assist_rate = mean(result == "assist", na.rm = TRUE),
avg_points = mean(points, na.rm = TRUE),
.groups = "drop"
)
return(screen_effectiveness)
}
# Movement efficiency score
calculate_movement_efficiency <- function(tracking_data, player_id) {
player_data <- analyze_movement_vectors(tracking_data, player_id)
# Calculate metrics
total_distance <- sum(player_data$distance, na.rm = TRUE)
touches <- sum(tracking_data$player_id == player_id &
tracking_data$has_ball == TRUE)
# Get outcomes after movement
movement_possessions <- player_data %>%
group_by(play_id) %>%
summarise(
distance = sum(distance, na.rm = TRUE),
max_speed = max(speed, na.rm = TRUE)
) %>%
filter(distance > 5) # Meaningful movement
efficiency <- list(
distance_per_touch = total_distance / touches,
active_possessions = nrow(movement_possessions),
avg_possession_distance = mean(movement_possessions$distance),
avg_max_speed = mean(movement_possessions$max_speed)
)
return(efficiency)
}
# Visualize off-ball movement patterns
plot_movement_patterns <- function(tracking_data, player_id, player_name) {
player_data <- analyze_movement_vectors(tracking_data, player_id)
# Filter for significant movements
movements <- player_data %>%
filter(speed > 5) # Only show meaningful movements
# Create plot
p <- ggplot() +
# Court outline
geom_rect(aes(xmin = 0, xmax = 94, ymin = 0, ymax = 50),
fill = "white", color = "black", size = 1) +
geom_vline(xintercept = 47, linetype = "dashed", color = "gray50") +
# Movement vectors
geom_segment(data = movements,
aes(x = x - dx, y = y - dy, xend = x, yend = y,
color = speed, alpha = speed),
arrow = arrow(length = unit(0.1, "inches")),
size = 0.5) +
scale_color_gradient(low = "blue", high = "red",
name = "Speed (ft/s)") +
scale_alpha_continuous(range = c(0.3, 0.9), guide = "none") +
labs(title = paste(player_name, "- Off-Ball Movement Vectors"),
subtitle = "Arrow direction shows movement direction, color shows speed",
x = "Court Position (feet)", y = "") +
theme_minimal() +
theme(
plot.title = element_text(size = 16, face = "bold"),
plot.subtitle = element_text(size = 10),
panel.grid = element_blank()
) +
coord_fixed(ratio = 1)
return(p)
}
# Cutting frequency analysis by court area
analyze_cutting_zones <- function(tracking_data, player_id) {
cuts <- tracking_data %>%
filter(player_id == !!player_id, has_ball == FALSE) %>%
arrange(play_id, frame) %>%
group_by(play_id) %>%
mutate(
dx = x - lag(x, default = first(x)),
dy = y - lag(y, default = first(y)),
speed = sqrt(dx^2 + dy^2) * 25,
dist_to_basket = pmin(
sqrt((x - 5.25)^2 + (y - 25)^2),
sqrt((x - 88.75)^2 + (y - 25)^2)
),
toward_basket = dist_to_basket < lag(dist_to_basket, default = Inf)
) %>%
filter(speed > 10, toward_basket == TRUE) %>%
mutate(
start_zone = case_when(
x < 19 ~ "Paint",
x >= 19 & x < 30 ~ "Mid-Range",
x >= 30 ~ "Perimeter"
)
) %>%
ungroup()
cut_summary <- cuts %>%
group_by(start_zone) %>%
summarise(
total_cuts = n(),
avg_speed = mean(speed),
avg_distance = mean(sqrt(dx^2 + dy^2)),
.groups = "drop"
)
return(cut_summary)
}
# Compare multiple players
compare_players_offball <- function(tracking_data, player_ids, player_names) {
comparison_data <- map2_dfr(player_ids, player_names, function(id, name) {
efficiency <- calculate_movement_efficiency(tracking_data, id)
tibble(
Player = name,
Distance_per_Touch = efficiency$distance_per_touch,
Active_Possessions = efficiency$active_possessions,
Avg_Speed = efficiency$avg_max_speed
)
})
# Create comparison plot
p <- comparison_data %>%
pivot_longer(cols = -Player, names_to = "Metric", values_to = "Value") %>%
ggplot(aes(x = Player, y = Value, fill = Player)) +
geom_col(show.legend = FALSE) +
facet_wrap(~Metric, scales = "free_y", ncol = 1) +
labs(title = "Off-Ball Movement Comparison",
x = "", y = "Value") +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.text.x = element_text(angle = 45, hjust = 1)
)
return(list(data = comparison_data, plot = p))
}
# Example Usage
if (interactive()) {
# Load data
tracking_data <- load_tracking_data("nba_tracking_data.csv")
screen_data <- read_csv("screen_data.csv")
# Analyze Stephen Curry
curry_id <- 201939
# Movement vectors
curry_movements <- analyze_movement_vectors(tracking_data, curry_id)
print(summary(curry_movements$speed))
# Spatial distribution
curry_spatial <- spatial_distribution_analysis(tracking_data, curry_id)
print(curry_spatial[1:4]) # Print statistics
# Movement clusters
curry_clusters <- cluster_movement_positions(tracking_data, curry_id)
print(curry_clusters$centers)
# Cutting zones
curry_cuts <- analyze_cutting_zones(tracking_data, curry_id)
print(curry_cuts)
# Visualize patterns
movement_plot <- plot_movement_patterns(tracking_data, curry_id,
"Stephen Curry")
print(movement_plot)
# Compare players
players <- c(201939, 202691, 203081)
names <- c("Stephen Curry", "Klay Thompson", "Bradley Beal")
comparison <- compare_players_offball(tracking_data, players, names)
print(comparison$data)
print(comparison$plot)
}
Elite Off-Ball Players
Stephen Curry
Movement Profile: The gold standard for off-ball play. Curry runs 2.4+ miles per game, constantly relocating, cutting, and screening. His gravity extends to 30+ feet, forcing defenses to account for him at all times.
Signature Moves: Split cuts through screens, relocations around multiple screens, backdoor cuts off defender overplays
Impact: Creates 1.2+ points per possession on off-ball cuts and relocations. Screen assists generate 15+ points per game for teammates
Klay Thompson
Movement Profile: Efficient, purposeful movement focused on getting open for catch-and-shoot opportunities. Excellent at reading defenders and using screens
Signature Moves: V-cuts off down screens, elevator cuts, corner relocations
Impact: 65+ eFG% on catch-and-shoot opportunities after movement, elite screen navigation
JJ Redick (Retired)
Movement Profile: Ran the most distance per game among guards for years. Masterful use of screens, exceptional conditioning
Signature Moves: Tight curls around pin-downs, flare relocations, constant baseline-to-baseline movement
Impact: 2.5+ miles per game, one of the highest catch-and-shoot volumes in NBA history
Richard Hamilton (Retired)
Movement Profile: Pioneered modern off-ball movement with endless running through screens. Famous for mid-range game created by constant motion
Signature Moves: Rip Hamilton screens, curl cuts, continuous baseline movement
Impact: Revolutionized shooting guard position, blueprint for modern off-ball play
Ray Allen (Retired)
Movement Profile: Precise, calculated movement to get open for three-point shots. Excellent timing on cuts and relocations
Signature Moves: Spot-up relocations, backdoor cuts, screen-the-screener actions
Impact: All-time great catch-and-shoot three-point shooter, clutch off-ball movement in critical moments
Current Elite Off-Ball Players
- Duncan Robinson: Extreme off-ball movement, 2.3+ miles per game, elite screen navigation
- Devin Booker: Versatile off-ball game, excellent cutter and relocator when not handling
- Khris Middleton: Smart movement, excellent at reading defensive rotations
- CJ McCollum: Creative off-ball movement, uses screens effectively
- Tyler Herro: Active relocator, good at finding open spots
Team Systems Emphasizing Movement
Golden State Warriors
Philosophy: Constant motion, off-ball screens, spacing. "Motion weak" offense with continuous movement and cutting
Key Elements: Screen the screener, split cuts, elevator screens, horns sets with multiple actions
Impact: Revolutionary offense averaging 115+ offensive rating in championship years. Forces defensive rotations through movement
San Antonio Spurs (Popovich Era)
Philosophy: Beautiful basketball through passing and off-ball movement. "0.5 basketball" - making quick decisions
Key Elements: Hammer cuts, flare screens, baseline movement, constant cutting to open spaces
Impact: Consistent top-5 offense for decades, emphasis on ball and player movement
Miami Heat
Philosophy: Structured movement with emphasis on spacing and cutting opportunities
Key Elements: Strong off-ball screening, corner relocations, baseline cuts
Impact: Maximizes efficiency through movement, particularly for shooters like Robinson and Herro
Boston Celtics (Recent)
Philosophy: Five-out spacing with constant off-ball movement and cutting
Key Elements: Flex cuts, screen-the-screener, weakside movement
Impact: Creates driving lanes through off-ball movement and spacing
Princeton Offense (College/NBA Variations)
Philosophy: Backdoor cuts, constant off-ball screening, read-and-react
Key Elements: Chin cuts, UCLA screens, backdoor layups off overplays
Impact: Systematic approach to creating high-percentage shots through movement
Strategic Applications
Creating Defensive Breakdowns
Help-Side Rotations: Off-ball cuts force help defenders to make decisions, creating open shots elsewhere
Screen Navigation Challenges: Multiple screens force switches, creating mismatches
Fatigue Factor: Constant movement wears down defenders physically and mentally
Spacing and Floor Balance
Five-Out Spacing: Constant movement maintains optimal spacing, creating driving lanes
Corner Filling: Quick relocations to corners after actions maintain three-point threat
Lift Actions: Coming up from baseline prevents defensive packing in paint
Countering Defensive Schemes
vs. Switch Everything: Screen-the-screener and slip screens exploit switching defenses
vs. Drop Coverage: Relocations behind the drop create open shots
vs. Blitz/Trap: Backdoor cuts and weak-side movement punish aggressive ball pressure
vs. Zone Defense: Cutting through gaps and relocating to dead zones breaks down zone structure
Maximizing Star Players
Off-Ball for Ball-Dominant Stars: Teaching stars like Luka or Harden to move without the ball when they don't have it maximizes offensive possessions
Complementary Pieces: Role players with excellent off-ball movement (3-and-D wings) maximize value alongside stars
Dual Threat Creation: Stars who can create on and off ball (Curry, Booker) are extremely valuable
Analytics-Driven Decision Making
Shot Quality from Movement: Shots after movement have higher eFG% than static possessions
Defensive Attention Metrics: Players who draw defensive attention without the ball create easier shots for teammates
Optimal Movement Patterns: Data analysis reveals most efficient cutting lanes, screen locations, and relocation spots
Player Development Focus
Conditioning Programs: Building endurance to maintain movement throughout games
Screen Navigation: Teaching players to use screens effectively, both setting and using them
Court Awareness: Reading defenses to know when to cut, when to space, when to screen
Timing and Chemistry: Developing feel for when teammates are ready to pass, building chemistry through repetition
Game Planning
Scouting Reports: Identify defenders who struggle navigating screens or tracking off-ball movement
Situational Movement: Design specific off-ball actions for end-of-game situations
Fatigue Exploitation: Increase off-ball movement late in games when defenders are tired
Conclusion
Off-ball movement is fundamental to modern basketball success. The combination of tracking data, advanced analytics, and systematic offensive design has elevated off-ball play to an art form. Elite teams and players understand that basketball is played by all five players constantly, not just the one with the ball.
The most successful offenses create advantages through continuous movement, intelligent cutting, and purposeful screening. As defensive schemes become more sophisticated, the importance of disciplined, data-informed off-ball movement will only increase. Players who master this aspect of the game—whether as stars like Stephen Curry or role players like Duncan Robinson—provide immense value to their teams and change the way defenses must approach the game.