In some cases it might be considered premature optimization, but in those cases where you know the error is (a) normal; (b) frequent; and (c) numerous, then you would want to factor that knowledge into the design. As for why it is expensive, throwing (raising) an error results in an allocation of the Error object with the stack information (used for debugging or catching an exception). Can't recall the numbers for Java but there has been some quantification of the relative speeds of using throw versus returning a result back that captures that an error occurred.

I also might note that there are other ways that libraries handle errors. Typical in floating point operations is to send a NaN (not-a-number) back as the result. The FFPs have a special encoding for this number. A function which returns a list might also return an empty list to indicate no matches. But these are all just variations on manipulating the return result so that errors are encoded within the return value.

Another method to deal with errors has to do with pre- and post-functions that tell you whether an error will occur or has occurred. For example, before you open a file for a read operation, you might want to check if the file exists. The open file operation might throw an exception if the file does not exist, but if you check first, you can avoid that error in the first place. Along similar lines, but less palatable is to have a function which verifies that no error occurs. For example, you might open the file but no error is signaled by an exception or the returned value. Instead, you could call an isOk function after you attempted the file read.

So tell you buddy to make sure to have these extra error handling mechanisms in place to allow the end user to decide how they want to handle the error. Better yet, allow the user to set a property in the object to indicate how they want to handle the errors (ErrorException, ErrorReturnValue, ErrorPreCheck, ErrorPostCheck, etc...).