Performance & Profiling
Profiling CPU and GPU bottlenecks in Unreal Engine, optimizing Tick, Blueprints, animation, collision, UI, draw calls, LODs, occlusion, instancing, and memory.
22 questions
JuniorTheoryVery commonWhat does it mean for a frame to be CPU-bound versus GPU-bound?
What does it mean for a frame to be CPU-bound versus GPU-bound?
A frame is CPU-bound when the processor's work (logic, draw-call setup) takes longest, and GPU-bound when the graphics card's work (shading, pixels) takes longest. The slower side caps frame rate, so profile first.
Common mistakes
- ✗Optimizing the GPU side when the frame is actually CPU-bound
- ✗Assuming the bound is fixed rather than changing from scene to scene
- ✗Guessing the bottleneck instead of measuring it with a profiler
Follow-up questions
- →Which console command shows whether a frame is CPU- or GPU-bound?
- →Name one cause of a CPU-bound frame and one of a GPU-bound frame.
JuniorTheoryVery commonWhat is an LOD (Level of Detail) and why does it matter?
What is an LOD (Level of Detail) and why does it matter?
An LOD is a simpler version of a mesh with fewer triangles, swapped in as the object moves farther away. Distant objects look nearly the same with less geometry, so LODs cut rendering cost without visible quality loss.
Common mistakes
- ✗Thinking lower LODs add detail instead of removing it from the mesh
- ✗Confusing LODs with lighting, shadow, or texture quality settings
- ✗Disabling LODs entirely and rendering full geometry at all distances
Follow-up questions
- →What controls when the engine switches from one LOD to the next?
- →How does an HLOD differ from a regular per-mesh LOD?
JuniorTheoryVery commonWhat is the Tick function and why is it a performance cost?
What is the Tick function and why is it a performance cost?
Tick is the per-frame update called on every enabled Actor and Component. The engine runs your logic once each frame, so many ticking objects multiply work. Disable Tick when unneeded or use timers.
Common mistakes
- ✗Leaving Tick enabled on Actors that have no per-frame logic to run
- ✗Assuming Tick runs at a fixed rate instead of varying with frame time
- ✗Putting heavy work in Tick instead of using a timer or event
Follow-up questions
- →How do you disable
Tickon an Actor that does not need it? - →When would a timer be a better choice than ticking every frame?
JuniorTheoryVery commonHow do you profile performance in Unreal Engine?
How do you profile performance in Unreal Engine?
Start with stat unit to see frame, game, draw, and GPU times, then drill in with stat game, stat gpu, or stat scenerendering. For deep traces use Unreal Insights, which captures CPU, GPU, and memory timelines.
Common mistakes
- ✗Trusting raw FPS without separating game-thread, render-thread, and GPU costs
- ✗Profiling only in the editor and ignoring packaged Development/Shipping builds
- ✗Thinking
stat fpsgives a per-system breakdown — it only shows the rate
Follow-up questions
- →What do the four numbers reported by
stat unitmean? - →Why should you profile a packaged build rather than the editor?
MiddleTheoryCommonCPU-bound versus GPU-bound — how do you tell which it is?
CPU-bound versus GPU-bound — how do you tell which it is?
Run stat unit and compare: if Game or Draw is the largest number you are CPU-bound; if GPU dominates you are GPU-bound. Frame time roughly equals the max of the threads, since CPU and GPU run in parallel.
Common mistakes
- ✗Assuming CPU and GPU run serially, so frame time is their sum rather than their max
- ✗Treating every FPS drop as a GPU problem and ignoring game-thread cost
- ✗Forgetting
Drawis a CPU (render-thread) cost, not a GPU cost
Follow-up questions
- →What does it mean when
Frameis larger than every individual thread time? - →How does VSync or a frame-rate cap distort a CPU-vs-GPU diagnosis?
MiddleTheoryCommonHow do you find expensive Tick functions in a project?
How do you find expensive Tick functions in a project?
Use stat game to see aggregate tick cost, then capture an Unreal Insights trace and inspect the game-thread timeline for Tick events. TickGroup rows and per-actor timing pinpoint which classes dominate the frame.
Common mistakes
- ✗Believing per-function tick cost is invisible and only total frame time is measurable
- ✗Profiling Tick cost on the GPU timeline instead of the game thread
- ✗Forgetting to capture a packaged build where tick counts match shipping
Follow-up questions
- →How does an
Unreal Insightstrace group ticks byTickGroup? - →Why might a Tick look cheap per call but still dominate the frame?
MiddleTheoryCommonWhat is instancing, and what are ISM and HISM in Unreal Engine?
What is instancing, and what are ISM and HISM in Unreal Engine?
Instancing draws many copies of one mesh in a single draw call. An ISM (Instanced Static Mesh) holds those instances; a HISM adds per-instance culling and automatic LOD selection, making it suited to large outdoor scatter.
Common mistakes
- ✗Thinking each instance is a separate mesh copy in memory
- ✗Swapping the roles of ISM and HISM — HISM is the one with culling and LOD
- ✗Believing instancing does not collapse draw calls
Follow-up questions
- →Why must all instances in an ISM share the same material set?
- →When does Nanite remove the need to choose ISM versus HISM?
MiddleTheoryCommonWhat are LODs and HLODs, and how do they differ?
What are LODs and HLODs, and how do they differ?
An LOD is a lower-detail version of one mesh swapped in by distance to cut triangles. HLOD merges many distant actors into a single combined proxy mesh, reducing both triangle count and draw calls for whole regions.
Common mistakes
- ✗Thinking LODs add detail up close rather than removing it at distance
- ✗Confusing LOD (per-mesh) with HLOD (many actors merged into one proxy)
- ✗Believing HLODs do not reduce draw calls
Follow-up questions
- →When does Nanite make manual mesh LODs unnecessary?
- →What artifacts can appear at an HLOD transition distance?
MiddleCodeCommonImplement object pooling for projectiles to avoid per-shot spawn cost
Implement object pooling for projectiles to avoid per-shot spawn cost
Pre-spawn a fixed set of projectile actors once, keep them deactivated, and hand out a free one on fire instead of calling SpawnActor. Return spent projectiles to the pool by deactivating them rather than calling Destroy.
Common mistakes
- ✗Calling
SpawnActor/Destroyinside the pool, defeating its whole purpose - ✗Leaving
Tickand collision enabled on idle pooled actors - ✗Failing to reset projectile state (velocity, damage, owner) on reuse
Follow-up questions
- →How do you handle the pool running dry under sustained fire?
- →Why must a returned projectile reset its velocity and collision state?
MiddleTheoryCommonWhat is occlusion culling and how does Unreal Engine use it?
What is occlusion culling and how does Unreal Engine use it?
Occlusion culling skips rendering objects hidden behind other geometry. Unreal uses hardware occlusion queries and a Hierarchical Z-Buffer test each frame, so occluded meshes issue no draw calls even though they are inside the frustum.
Common mistakes
- ✗Confusing occlusion culling with frustum culling
- ✗Thinking culled objects are destroyed rather than just skipped for rendering
- ✗Assuming occlusion is baked once and cannot handle dynamic objects
Follow-up questions
- →Why can occlusion queries cause a one-frame visibility latency?
- →When are precomputed visibility volumes preferable to runtime queries?
MiddleTheoryCommonHow do you optimize Blueprint performance in Unreal Engine?
How do you optimize Blueprint performance in Unreal Engine?
Avoid per-frame work in Event Tick, prefer events and timers, and enable Blueprint Nativization or move hot logic to C++. Cache references instead of repeated Get and Cast calls, and trim heavy nodes from loops.
Common mistakes
- ✗Assuming Blueprint runs at native C++ speed with no VM overhead
- ✗Calling
Castevery frame instead of caching the casted reference - ✗Stuffing per-frame logic into
Event Tickrather than using events
Follow-up questions
- →Why is a
Castnode in a tight loop a performance concern? - →When is it worth moving a Blueprint function entirely into C++?
MiddleTheoryCommonHow do you optimize collision performance in Unreal Engine?
How do you optimize collision performance in Unreal Engine?
Use simple collision primitives instead of per-triangle complex collision, disable collision on meshes that never need it, and tune collision channels and object types so queries test only relevant objects. Avoid overlapping query volumes.
Common mistakes
- ✗Using complex (per-triangle) collision where a simple primitive would suffice
- ✗Leaving collision enabled on purely decorative meshes
- ✗Putting every object on one channel so queries test irrelevant actors
Follow-up questions
- →Why is complex collision much more expensive than a box or capsule?
- →How do trace channels reduce the cost of a line trace?
MiddleTheoryCommonHow do you optimize spawning and destroying many Actors at runtime?
How do you optimize spawning and destroying many Actors at runtime?
Use object pooling: pre-spawn actors once and recycle them by toggling visibility, collision, and Tick instead of calling SpawnActor/Destroy. This avoids per-spawn allocation, component registration, and garbage-collection pressure.
Common mistakes
- ✗Calling
SpawnActor/Destroyin a tight loop and blaming the GC for hitches - ✗Believing
SpawnActoris thread-safe and can run off the game thread - ✗Thinking immediate
Destroyfrees memory instantly rather than queuing for GC
Follow-up questions
- →What state must a pooled actor reset when it is reactivated?
- →How does deferred spawning (
SpawnActorDeferred) help split spawn cost?
MiddleTheoryCommonHow do you reduce draw calls in an Unreal Engine scene?
How do you reduce draw calls in an Unreal Engine scene?
Merge static meshes, use Instanced Static Meshes for repeated assets, and share materials so the renderer can batch. Fewer unique mesh-material pairs and aggressive culling cut the number of draw calls the render thread issues.
Common mistakes
- ✗Believing draw calls are a GPU cost rather than a render-thread (CPU) cost
- ✗Giving each object a unique material, defeating batching
- ✗Thinking instancing increases draw calls instead of collapsing them into one
Follow-up questions
- →Why does sharing a material let two meshes batch into one draw call?
- →What is the trade-off between merged meshes and per-mesh culling?
MiddleTheoryCommonHow do you reduce Tick usage across a project?
How do you reduce Tick usage across a project?
Disable Tick by default (bCanEverTick = false) and enable it only where needed. Replace per-frame polling with timers, events, or delegates, and raise TickInterval so logic that does not need every frame runs less often.
Common mistakes
- ✗Leaving
bCanEverTickenabled on actors that never need per-frame logic - ✗Polling a condition every frame instead of subscribing to an event
- ✗Not knowing
TickIntervalcan throttle a tick to run less often
Follow-up questions
- →When is a timer a better fit than a throttled Tick?
- →How does
bStartWithTickEnableddiffer frombCanEverTick?
MiddleTheoryOccasionalWhat is the performance cost of dynamic shadows in Unreal Engine?
What is the performance cost of dynamic shadows in Unreal Engine?
Dynamic (movable) lights re-render the scene into shadow depth maps every frame, multiplying geometry passes and draw calls. Static and stationary lights bake shadows offline, so they are far cheaper at runtime.
Common mistakes
- ✗Believing movable and static lights cost the same at runtime
- ✗Thinking dynamic shadows are baked rather than re-rendered every frame
- ✗Ignoring that each shadow-casting object adds a geometry pass per dynamic light
Follow-up questions
- →How do shadow-cascade settings trade quality against cost for a directional light?
- →When is a stationary light a good compromise between static and movable?
MiddleCodeOccasionalImplement a UMG health bar that updates without ticking every frame
Implement a UMG health bar that updates without ticking every frame
Drive the bar from a delegate: the health component broadcasts an OnHealthChanged event, and the widget updates its ProgressBar only in that handler. No Event Tick and no per-frame property binding are needed.
Common mistakes
- ✗Using a UMG property binding, which silently re-evaluates every frame
- ✗Polling health in
NativeTickinstead of reacting to a change event - ✗Forgetting to set the bar to its current value once on construction
Follow-up questions
- →Why is a UMG property binding effectively a per-frame
Tick? - →How do you ensure the bar shows the correct value before the first event fires?
MiddleTheoryOccasionalHow do you optimize UMG and UI performance in Unreal Engine?
How do you optimize UMG and UI performance in Unreal Engine?
Replace per-frame property bindings with event-driven updates, set static widgets to SelfHitTestInvisible, mark unchanging subtrees as Is Volatile = false so Slate caches them, and use Invalidation Box to skip redrawing stable UI.
Common mistakes
- ✗Binding many widget properties per frame instead of updating them on events
- ✗Leaving decorative widgets
Visibleand hit-testable instead of invisible - ✗Thinking
Invalidation Boxcosts more than it saves for stable UI
Follow-up questions
- →How does an
Invalidation Boxdecide which children to redraw? - →Why is a property binding more expensive than an explicit event update?
MiddleTheoryOccasionalHow do you profile memory usage in Unreal Engine?
How do you profile memory usage in Unreal Engine?
Use stat memory and Memreport for a snapshot, the Memory Insights track in Unreal Insights for live allocation traces, and obj list to break down UObject counts. Profile a packaged build for realistic figures.
Common mistakes
- ✗Thinking Unreal has no built-in memory tooling and only an OS profiler works
- ✗Profiling memory only in the editor, where asset and instrumentation overhead skew figures
- ✗Confusing
stat memorytotals with per-asset or per-class breakdowns
Follow-up questions
- →What does a
Memreportfile include thatstat memorydoes not? - →How do you track a memory leak that grows slowly across many minutes?
SeniorTheoryOccasionalHow do you optimize animation performance for many characters?
How do you optimize animation performance for many characters?
Enable URO (Update Rate Optimization) so distant skeletal meshes evaluate less often, use animation LODs, and move heavy logic into Fast Path or threaded Animation Blueprint updates. Cull or freeze meshes that are off-screen.
Common mistakes
- ✗Believing distant characters cost the same as close ones without URO or LODs
- ✗Assuming bone evaluation runs on the GPU rather than the CPU
- ✗Putting heavy logic in the AnimBP event graph instead of the threaded update
Follow-up questions
- →Why must AnimBP
Fast Pathlogic avoid function calls andCastnodes? - →How do animation LODs differ from mesh LODs?
SeniorDebuggingRareHow do you debug intermittent frame hitching in a packaged build?
How do you debug intermittent frame hitching in a packaged build?
Capture an Unreal Insights trace and find the spiking frame, then read which track widened — game thread, GPU, GC, or asset streaming. The spike's call stack names the culprit; stat unit only confirms a hitch happened. Here the game thread is blocked by CollectGarbage for most of the frame on an even multi-second cadence — a GC pause, not GPU or streaming work.
Common mistakes
- ✗Trusting average FPS, which hides a single-frame spike
- ✗Assuming hitches are always GPU-bound and ignoring GC or streaming spikes
- ✗Debugging only in the editor where streaming and GC behave differently
Follow-up questions
- →How would you confirm a hitch is caused by a garbage-collection pause?
- →What settings reduce hitching from synchronous asset loading?
SeniorDebuggingRareHow would you approach a 30 FPS regression after adding a new gameplay system?
How would you approach a 30 FPS regression after adding a new gameplay system?
Profile before and after with stat unit to see which thread regressed, then capture an Unreal Insights trace to locate the new system's cost. Bisect: disable the system to confirm it is the cause before optimizing. Here game time tripled while draw and GPU held — a game-thread regression; the trace shows a synchronous pathfind in every crowd agent's per-frame Tick, multiplied across all agents.
Common mistakes
- ✗Optimizing before measuring which thread actually regressed
- ✗Skipping the bisect step that confirms the new system is the cause
- ✗Assuming a gameplay system can only cost game-thread time, never render or GPU
Follow-up questions
- →How do you tell a render-thread regression from a game-thread one in the trace?
- →Why is a baseline capture before the change essential for this workflow?