Optimize a canvas with thousands of moving objects

Tests GPU-bound vs CPU-bound bottleneck diagnosis. Strong answers: batch draw calls to cut JS-to-GPU overhead, and render to a smaller back buffer to reduce fill-rate cost.
WHAT THIS TESTS: Whether you can identify the two most common WebGL pipeline bottlenecks when object count is high: excessive draw-call overhead and oversized back-buffer fill rate. The interviewer is checking if you know the difference between JavaScript-side work and GPU-side work, and whether you reach for pipeline-level fixes instead of generic language tricks.
A GOOD ANSWER COVERS: First, batch draw calls. The MDN guide explicitly recommends batching draw calls because each separate call incurs driver validation and state-tracking overhead. For thousands of objects, you should collapse individual submissions into fewer commands. One approach is merging geometry into a single interleaved vertex buffer so one drawArrays call renders many objects. Another is using ANGLE_instanced_arrays, which the reference notes is universally supported in WebGL 1, to issue one instanced draw call with per-instance transforms stored in a separate attribute buffer. Either technique cuts JavaScript-to-native overhead from thousands of API calls down to one or a handful.
Second, consider rendering to a smaller back buffer and manage devicePixelRatio carefully. The reference states that high-DPI rendering can be expensive and recommends considering a smaller back buffer. Drawing at full native resolution on a retina display quadruples fragment-shader work and memory bandwidth; instead render to a reduced-resolution framebuffer and let the browser upscale, or cap devicePixelRatio at a reasonable limit, to cut per-pixel cost. This is especially effective when objects overlap or use complex fragment shaders.
COMMON WRONG ANSWERS: Proposing generic frame-rate limiters or event debouncing that ignore the GPU pipeline entirely. Suggesting Web Workers for canvas rendering is another red flag because the reference context is single-threaded WebGL and the main thread must issue commands. Recommending alpha:false is also wrong here; the reference warns that alpha:false can be expensive, so it is not an optimization to reach for blindly. Similarly, suggesting eager deletion of objects misses the point when the issue is active rendering load, not memory pressure.
LIKELY FOLLOW-UPS: How would you measure whether the bottleneck is draw-call overhead versus fill rate? What are the trade-offs of reducing back-buffer resolution on text readability? How do you update buffer data each frame without causing pipeline stalls? When is instancing not applicable? Would you use requestAnimationFrame differently?
ONE CONCRETE EXAMPLE: A dashboard with five thousand moving nodes. Without batching, each node triggers its own draw call and the CPU spends more time in the driver than the GPU spends drawing. By merging node geometry into one interleaved buffer and issuing a single drawArrays call, or using instanced arrays with a single drawArraysInstanced call, you reduce thousands of API calls to one. Then you halve the back-buffer dimensions so the fragment shader runs one quarter as many times. Together these typically cut frame time from thirty milliseconds to under five.
Source: developer.mozilla.org
Read the original → developer.mozilla.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.