Good question! I would clarify the answer a little bit, though:
... the result returns the data type of the expression with the highest data type precedence.
should probably be
... the result returns the data type of the non-NULL expression with the highest data type precedence.
This is why rows 1 and 2 pass -- even though zip is an int as defined by the column, its data type is not considered in these rows because it is NULL.
No, it certainly shouldn't. The original explanation is correct. Your version is incorrect.
The types of any NULL arguments to coalesce and considered along with the types of the non-null arguments when determining what type the result must be.
The argument zip is considered every time, when determining the highest precedence type, whether it's null or not. If the third row considered had been (null','a2',null) instead of (null,'a2',111) we would have had exactly the same failure on exactly the same row for exactly the same reason as with the values in the question.
Don't forget that in SQL NULLs have type; a NULL value in an int column or for an int variable has type int, as of course does the constant expression cast(NULL as int).
Back in January I had a QotD aiming to help people learn how typed nulls and coalesce interact:Coalesce and Conversion[/url], it may help you to look at that.