Pseudo-classes
The HTML is a list: one ul holding three li items, Tea, Milk and Bread. The browser makes an element for each tag and draws them with bullets. Nothing in the tags says which item is first or which one the mouse is on.
A pseudo-class is a keyword after one colon, such as :first-child or :hover. It picks whole elements by a fact the tags do not spell out: their place among their parent's children, or what the user is doing right now.
The browser counts each li's place among the ul's children: 1, 2, 3. Only Tea is number 1, so li:first-child matches Tea alone. Put a new item at the top and the match moves to it, with no change to the CSS.
The declaration applies to the matched item only. Tea turns bold, which is weight 700 instead of the normal 400. Milk and Bread stay normal, because the selector did not match them.
li:hover matches an li while the pointer, such as a mouse, is over it. Right now the pointer is away from the list, so the rule matches nothing and no item is tomato. The rule stays ready in the stylesheet.
The pointer moves over Milk. Its state changes, so the browser checks the selectors again: li:hover now matches Milk, and Milk turns tomato. Move the pointer away and the match ends, so the colour goes back.
Pseudo-elements
A pseudo-element comes after two colons, such as ::before. It does not pick a whole element. It names a part of one, or here a new box CSS adds as the first child of each li. The content property fills it.
li::before matches all three items, so the browser makes three new boxes, each holding a tick and a space. Each box belongs to its li and is drawn before its words. The pointer has left, so Milk is black again.
The ::before boxes are not elements. The HTML still has three li tags and the DOM, the browser's tree of elements, still has three li. CSS adds them only to what is drawn. Screen readers may skip them, so keep real words in the HTML.
Telling them apart
One colon marks a pseudo-class: it picks whole elements by state or place and counts like a class in specificity. Two colons mark a pseudo-element: a part or a new box, counted like a type selector. Browsers still accept old :before too.
What happens
;; step 1/10 · list
index.html
<ul>
<li>Tea</li>
<li>Milk</li>
<li>Bread</li>
</ul>
elements (the DOM): ul with 3 li children
li 1 "Tea"
li 2 "Milk"
li 3 "Bread"
browser default: ul { list-style-type: disc; }
A real browser works out every rule at once. The figure brings in one rule at a time so you can see what each one does. The page drawing is enlarged. The bullet is a pseudo-element too, ::marker. By default it sits outside the li's content, so the ::before box comes after the bullet and before the words.