Calling notifyDataSetChanged() redraws the entire list — wasteful and visually jarring for small changes. ListAdapter with DiffUtil calculates the minimal set of changes and animates just those rows:

class UserDiffCallback : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(oldItem: User, newItem: User) =
        oldItem.id == newItem.id

    override fun areContentsTheSame(oldItem: User, newItem: User) =
        oldItem == newItem   // relies on data class structural equality
}

class UserAdapter : ListAdapter<User, UserAdapter.UserViewHolder>(UserDiffCallback()) {
    class UserViewHolder(val binding: ItemUserBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val binding = ItemUserBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return UserViewHolder(binding)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.binding.nameText.text = getItem(position).name
    }
}

// Updating the list is now a single call:
userAdapter.submitList(newUserList)

Notice this is exactly why making User a data class in Module 1 matters here — its auto-generated equals() is what powers areContentsTheSame.