For simple requests (GET, HEAD, or a POST with a basic content type like text/plain or application/x-www-form-urlencoded), the browser sends the request directly and adds an Origin header. The server replies with Access-Control-Allow-Origin set to either the requesting origin or a wildcard *. If that header is missing or does not match, the browser throws away the response and the JavaScript sees a CORS error, even though the server already ran the request.
For anything more involved (a PUT or DELETE, a JSON body with Content-Type: application/json, or custom headers like Authorization or X-Api-Key), the browser first sends a preflight request. This is an OPTIONS request that asks the server what is allowed. The server answers with Access-Control-Allow-Methods, Access-Control-Allow-Headers, and Access-Control-Allow-Origin. Only if the preflight passes does the browser send the real request.
Credentials add a strict rule. If the request sends cookies or HTTP auth, the server must send Access-Control-Allow-Credentials: true and must echo back the exact origin in Access-Control-Allow-Origin. A wildcard * is forbidden with credentials. To avoid repeating preflights, the server can send Access-Control-Max-Age, which tells the browser to cache the preflight result for a number of seconds (Chrome caps this at 7200 seconds, or 2 hours).