Стандартный кейс уже выше описали, но он не совсем прозрачный. Если обобщить: в котлине везде надо использовать обычные val/var переменные.
Lateinit на мой взгляд костыль стараюсь использовать его только для di полей.
С nullable переменными история отдельная. Обычно null делают либо для опциональный параметров либо для полей которые необходимо чистить, чтобы не утекало ничего при жизненном цикле компонентов.
К примеру:
Поставляемая через di ViewModel может быть lateinit var.
А binding (или ещё callback какие нибудь) nullable, чтобы их очистить в onDestroy/View.
И так как есть контракт их очищения именно там, везде до можно обращаться без проверки !!. Правильно ли это ? Хз, кто то топит за то чтобы было минимум внештатных ситуаций и даже если уверен что не null использовать ? А не !!. С другой стороны топят за !!, так как если есть явный контракт, то пусть лучше упадет и об этом станет известно, чем просто ничего не сделает))) более детально можно в лс спросить если что