Running this on a table containing consecutive integers from 1 - 60,000
takes just over 5 minutes on my machine.
DECLARE @validatedIDList NVARCHAR(max)
SELECT @validatedIDList = COALESCE(@validatedIDList + ', ', '') +
CAST(si.SelectedID AS NVARCHAR(50))
FROM dbo.SelectedID si
SELECT internal_objects_alloc_page_count,
internal_objects_dealloc_page_count,
internal_objects_alloc_page_count - internal_objects_dealloc_page_count
FROM sys.dm_db_task_space_usage
WHERE session_id = @@SPID
SELECT DATALENGTH(@validatedIDList)
Profiling the process shows that it spends a lot of time performing Blob manipulation

To answer the question why this is so slow nvarchar(max)
variables are stored as LOB
data in tempdb
on 8KB pages.
The final length of the string is 817,784 bytes (approx one hundred 8KB pages). The result of the query above is
+-----------------------------------+-------------------------------------+-------+
| internal_objects_alloc_page_count | internal_objects_dealloc_page_count | |
+-----------------------------------+-------------------------------------+-------+
| 5571528 | 5571424 | 104 |
+-----------------------------------+-------------------------------------+-------+
Showing the final string actually consumes 104 pages in tempdb
.
Dividing the alloc_page_count
by 60,000
shows that the average number of pages allocated and deallocated per assignment is 93.
Each concatenation operation does not just append to the existing LOB data but instead makes a copy of it. As the string grows longer the amount of work per concatenation grows accordingly.
The XML PATH
method is much more efficiently implemented and has the additional "bonus" that it is actually documented to work (unlike the method in the question)
The article Concatenating Row Values in Transact-SQL has a good round up of the available methods.