Every Lit component moves through a predictable sequence of stages, from construction through repeated updates to eventual disconnection. This lesson maps out the full lifecycle and every hook available at each stage.
The Three Phases of a Lit Component's Life
A Lit component's lifecycle breaks into three broad phases: a one-time setup phase (constructor, then connectedCallback when added to the DOM), a repeating update phase that runs every time reactive properties change (shouldUpdate, willUpdate, render, updated), and a teardown phase (disconnectedCallback) when the element is removed.
Most of these hooks come from the native Custom Elements spec (connectedCallback, disconnectedCallback) or from ReactiveElement's update cycle (shouldUpdate, willUpdate, updated, firstUpdated) — Lit doesn't invent a parallel lifecycle, it extends the platform's own hooks with reactive update scheduling.
Logging every hook like this once, in a throwaway component, is the fastest way to build an intuitive feel for the exact order these methods run in.
Lifecycle Method Order
constructor()
-> connectedCallback()
-> willUpdate(changedProps) [before every render]
-> render()
-> updated(changedProps) [after every render]
-> firstUpdated(changedProps) [only after the very first render]
-> disconnectedCallback() [when removed from the DOM]
constructor() and connectedCallback() each run exactly once per element instance (per DOM connection).
willUpdate, render, and updated run together as a group on every single reactive update.
firstUpdated runs only once, immediately after the very first updated call.
disconnectedCallback runs when the element is removed from the document, and can run again if it's later re-attached and re-removed.
Lifecycle Hooks Cheat Sheet
Every hook, when it runs, and what it's typically used for.
Hook
Runs
Typical Use
constructor()
Once, on instantiation
Set default property values
connectedCallback()
Each time added to the DOM
Start subscriptions, fetch data
shouldUpdate(changed)
Before every update, can cancel it
Performance: skip unnecessary renders
willUpdate(changed)
Before every render, sync style
Compute derived state before rendering
render()
Every update
Return the template
updated(changed)
After every render
React to specific property changes, focus management
firstUpdated(changed)
Once, after first render
One-time DOM setup, measuring elements
disconnectedCallback()
Each time removed from the DOM
Clean up subscriptions, timers, observers
One-Time Hooks vs. Repeating Hooks
It's important to separate hooks that run once per connection (constructor, connectedCallback, firstUpdated) from hooks that run on every single reactive update (shouldUpdate, willUpdate, render, updated). Placing expensive one-time setup logic inside a repeating hook by mistake is a common source of performance bugs.
Hook
Frequency
constructor()
Once per instance
connectedCallback()
Once per DOM connection (can repeat if moved/re-added)
firstUpdated()
Exactly once, ever, per instance
willUpdate() / render() / updated()
Every single reactive update
Reacting to a Specific Property Change
The changedProperties argument passed to willUpdate and updated is a Map of property names to their *previous* values, letting you react precisely to which property changed rather than re-running logic for every update regardless of cause.
updated(changedProperties) {
if (changedProperties.has('selectedId')) {
this.#scrollSelectedItemIntoView();
}
}
Checking changedProperties.has(...) avoids running expensive logic (like scrolling or measuring) on unrelated updates.
Common Mistakes
Forgetting to call super.connectedCallback()/super.disconnectedCallback() when overriding them, breaking Lit's own internal setup/teardown.
Putting one-time setup logic inside render() or updated(), causing it to re-run on every single update.
Not cleaning up subscriptions, timers, or event listeners started in connectedCallback() inside the matching disconnectedCallback().
Reacting to every update inside updated() without checking changedProperties, causing unnecessary repeated work.
Key Takeaways
The lifecycle has three phases: one-time setup, a repeating update cycle, and teardown.
constructor, connectedCallback, and firstUpdated are one-time (per connection); willUpdate/render/updated repeat on every change.
Always call super.connectedCallback() and super.disconnectedCallback() when overriding them.
Use the changedProperties map to react precisely to which property changed, rather than to every update.
Pro Tip
Keep a mental (or literal, commented) checklist pairing every connectedCallback() side effect with its disconnectedCallback() cleanup — subscriptions, timers, and observers started without a matching teardown are the single most common source of memory leaks in real Lit components.
You now have the full lifecycle map. Next, look closely at connectedCallback specifically.