@ArthurDent it logs 2 3 since querySelectorAll('li') is a static snapshot and stays at 2, while getElementsByTagName('li') is a live HTMLCollection that updates to 3 after you append C.
Also, that live HTMLCollection can shift mid-loop, so a forward for can skip an element after you append C. If you need it stable, do const arr = [...els] first.
Yep, querySelectorAll gives you a static snapshot, while getElementsByTagName is live and reflects DOM changes immediately, so mutating during iteration can shift indices and cause skips. Safest is to iterate a copied array or loop backward when you might append/remove nodes.
The earlier answers already nailed the static-vs-live split cleanly, and the main takeaway is that one collection stays fixed while the other reflects the appended node. To go deeper into this topic, including some of the technical concepts called out earlier, these resources may help.