Long Tasks: What they are and why you should avoid them
All web applications are granted a single thread, the Main Thread, which is responsible for:
- Handling user input events, like clicks and keyboard events
- Generating frames to present pixels to the user
Each of these operations is executed in what's known as a Task.
Since there is only one Main Thread responsible for all these Tasks, any Task that takes a particularly long time to execute will clog up the thread and degrade user experience.
In this tip, we'll examine these long-running Tasks, called Long Tasks, and why you should avoid them.
A Task is considered a Long Task if it takes longer than 50ms.
As we know from my tip on the browser event loop, the Main Thread can only run one Task at a time. Any Task that is not actively running is queued in the browser's Task Queue:
While a Task is executing on the Main Thread, the Event Loop can not unload other queued Tasks from the Task Queue onto the thread.
Long Tasks are particularly problematic because they block the Event Loop from unloading the Task Queue for an extended period of time; they essentially block the Event Loop from executing any further work on the Main Thread.
The browser event loop occasionally will run the Render Steps instead of selecting a Task from the Task Queue. The Render Steps Task is responsible for presenting frames to the user's screen.
Like other Tasks, the Render Steps cannot run while another Task is running on the thread. As a result, a Long Task can severely degrade the browser's ability to generate frames to your user:
For example, if your app relies on React to present its initial frame, React must construct the DOM, and then the Render Steps must run, and then finally your user can see your UI:
Your user cannot see your UI until a Frame is produced, so it's important to reduce the size of your Long Task so the browser can produce the frame sooner:
Let's consider another example: a user is typing into an input box.
Each keypress event is queued in the browser's Task Queue and subsequently place onto the Main Thread to run by the Event Loop. The keypress is then represented on-screen via a Frame:
We want input to feel responsive and smooth for users, like this:
Notice that each keypress of the user is immediately represented as pixels on the screen via a Frame.
If a Long Task occurs while typing, your user will experience jank:
For a user, input jank will manifest as delayed frames to reflect their input events:
Common examples of these codepaths include:
forloops with a high iteration count
- String parsing, decoding, concatenation, etc.
- Updates to the DOM
- Compilation of large scripts
- Inefficient querying of element positioning information, inducing Layout Thrashing
- Heavy or frequent Microtasks, usually from resolved
If you collect a trace of your web application, the Chromium F12 Profiler will help you identify Long Tasks by flagging them in the UI:
The profiler will represent Long Tasks as a flamegraph. Follow my tip on understanding flamegraphs to learn how to read these graphs, and identify slow codepaths within a Task.
We've covered what Long Tasks are, and why you should avoid them.
Explore the following tips on how to optimize your Long Tasks and improve your user experience:
- Remove inefficient work from Tasks
- Defer non-critical work from your critical path
- Decompose Long Tasks into smaller tasks