From https://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html:
Note that the fail-fast behavior of an iterator cannot be guaranteed
as it is, generally speaking, impossible to make any hard guarantees
in the presence of unsynchronized concurrent modification. Fail-fast
iterators throw ConcurrentModificationException on a best-effort
basis. Therefore, it would be wrong to write a program that depended
on this exception for its correctness: the fail-fast behavior of
iterators should be used only to detect bugs.
That is, the iterator will try its best to throw exception, but isn't guaranteed to do so in all cases.
Here are some more links on how fail fast iterators works and how the are implemented - in case someone will be interested:
http://www.certpal.com/blogs/2009/09/iterators-fail-fast-vs-fail-safe/
http://javahungry.blogspot.com/2014/04/fail-fast-iterator-vs-fail-safe-iterator-difference-with-example-in-java.html
http://www.javaperformancetuning.com/articles/fastfail2.shtml
And here is another SO question where people trying to find out the same thing:
Why isn't this code causing a ConcurrentModificationException?