Android Rect: Mutable Geometry Without the Allocation Tax
Android's Rect class enables zero-allocation geometry math in UI code, but its mutable API demands discipline. Learn when to reuse instances for hit-testing performance and when to copy to avoid race conditions and stale-state bugs.
28 Aug 2025, 09:41 UTC

The allocation problem in UI loops
Custom views and touch handling run on the UI thread, often at 60 or 120 fps. Allocating a new Rect for every hit-test or layout calculation adds up: thousands of short-lived objects per frame, triggering GC pauses that manifest as jank. The Android framework itself avoids this pattern internally—View and ViewGroup reuse a handful of Rect instances during measure, layout, and draw passes.
Rect's mutable design: zero-allocation geometry
android.graphics.Rect is a lightweight mutable struct with four public int fields: left, top, right, bottom. Methods like set(int l, int t, int r, int b), offset(int dx, int dy), and union(Rect r) mutate the same instance. contains(int x, int y) and intersects(Rect r) are inlined integer comparisons—no object creation, no virtual dispatch overhead. This makes Rect ideal for tight loops where you need to test dozens of regions per frame.
Worked example: hit-testing in a custom View
Consider a drawing app with a variable number of selectable layers. Each layer maintains its bounds in a pre-allocated Rect. In onTouchEvent we reuse a single Rect for the touch point, avoiding per-event allocation:
public class LayerView extends View {
private final List<Layer> layers = new ArrayList<>();
private final Rect touchRect = new Rect(); // allocated once
private final Rect layerBounds = new Rect(); // reused per layer
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
touchRect.set(x, y, x + 1, y + 1); // point as 1x1 rect
for (int i = layers.size() - 1; i >= 0; i--) {
Layer layer = layers.get(i);
layer.getBounds(layerBounds); // fills reused instance
if (layerBounds.contains(touchRect)) {
selectLayer(layer);
return true;
}
}
return super.onTouchEvent(event);
}
}
Layer.getBounds(Rect out) copies its internal coordinates into the supplied instance. The loop mutates layerBounds each iteration—no new objects. Profiling this with Android Studio's CPU profiler shows near-zero allocation rate during continuous touch drag.
The catch: thread safety and reuse pitfalls
Rect has no synchronization. Sharing a single instance across threads—say, a background layout calculator and the UI thread—creates race conditions. Field writes are not atomic for 32-bit ints on all architectures, and compound operations like offset() (which updates two fields) can leave the rectangle in an inconsistent state if interrupted.
Another subtle bug: reusing a Rect across layout passes without fully re-initializing it. If you call set(left, top, right, bottom) but forget one field, the stale value persists. Always use the four-argument set() or setRect(Rect src) to guarantee a clean slate.
Practical verification steps
- Profile allocations: Run a layout pass that allocates
new Rect()per child versus one that reuses a pre-allocated instance. Compare allocation counts in the Memory Profiler. - Stress-test thread sharing: Spin two threads that repeatedly call
rect.set(random, random, random, random)andrect.offset(1, 1)on the same instance. Log coordinates after each operation; mismatchedleft > rightortop > bottomindicates corruption. - Benchmark hit-test throughput: Execute 10,000
contains()calls in a loop using a reusedRectversus manualx >= left && x < right ...arithmetic. The difference is typically under 5%, confirming the inlined methods are not slower than hand-written checks.
When to use, when to copy
Use a reused Rect for:
- Per-frame hit-testing in onTouchEvent or onDraw
- Temporary coordinate transformations inside a single method
- Layout passes where you control the entire call chain
Create a new Rect (or call new Rect(existing)) when:
- Passing bounds to an external API you don't control
- Storing geometry in a data structure that outlives the current frame
- Sharing between threads without external locking
The mutable API is a performance tool, not a free lunch. Treat Rect like a stack-allocated struct in C: powerful in a controlled scope, dangerous when it escapes.