Huh? No. "Exceptional problems" are ones where the only thing that the code does is report the error. Whether a problem is exceptional is dependent on whether or not it's part of the normal workflow.
This is why there are, for example, 2 integer parsing methods in many languages, one that returns false for strings that aren't integers and one that throws an exception. It's up to the user of the function whether they have a way to handle the exception or whether they just want to report it to the user and exit.
> My rule of thumb is to use an exception whenever the function cannot complete its contract.
Exceptions that a function might throw are part of its contract.
> If I have a function LogIn(username, password), I expect to be logged in after the function completes.
That's a terrible design. You've violated a ton of different principles: single responsibility principle being the biggest.
For this I would have something like `authorize(username, password)` that figures out whether the username/password is valid, and then `login()` that actually creates the session. I might have another function `tryLogin(username, password)` that tries to login and returns some error info on failure.
In your system, what happens when a login happens and slips past your exception handler because it's used in a place that you didn't expect it to be used, and there's no type checking on exceptions? You just let the whole system go down. What happens when you want to log in with an auth token instead of a username/password? Have fun separating out your session logic. What happens when you want to write stress tests against your login system and now have to piece together a test suite that covers all cases for a monolith because you don't have any units to unit test? You rewrite the whole thing is what happens.
> Anything that trickles back up to the user and is displayed as an error message will have lag based on the user's reading speed.
Or it will cause lag for your entire server because wrong logins are fucking your stack and therefore your garbage collection.
My rule of thumb is to use an exception whenever the function cannot complete its contract. After that, it is up to the calling function to fix the problem, or however high up the stack until some function knows what is going on. If I have a function LogIn(username, password), I expect to be logged in after the function completes. Anything else, and I should have a exception thrown, whether it be due to lost connection, incorrect username/password, or anything else. On the other hand, if I have a function AttemptLogIn(username, password), then it would be sufficient for the function to return a boolean for success/failure.
I would modify the statement to "Use Exceptions for Infrequent Problems". Interacting with a user? Sure. Anything that trickles back up to the user and is displayed as an error message will have lag based on the user's reading speed. During heavy numerical calculations? No.