Your issue must be that at some point your SELECT statement doesn't find any value and doesn't assign anything new to your @catID variable and it keeps exactly the same as it was in previous loop. As a result - you never exit your while statement.
Code snippet to explain that:
DECLARE @catID INT = 5;
DECLARE @Test TABLE
(
CategoryID INT
);
/**
* Notice that I do not insert into table intentionally,
* so that following query doesn't assign anything
* to @catID variable
*/
SELECT TOP (1) @catID = CategoryID
FROM @Test;
SELECT @catID AS [@catID];
Result:
@catID
-----------
5
This query returns 5, and not NULL as you would expect.
Assigning value using subquery will either assign an actual value or NULL if nothing's found, that means when there's no category higher than @catID it will become NULL and your query will escape loop.
DECLARE @catID INT;
SET @catID = (
SELECT TOP (1) CategoryID
FROM Production.Categories
ORDER BY CategoryID
);
WHILE @catID IS NOT NULL
BEGIN
PRINT @catID;
SET @catID = (
SELECT TOP (1) CategoryID
FROM Production.Categories
WHERE CategoryID > @catID
ORDER BY CategoryID
);
END