I took Sean's excellent solution and tweaked it to avoid a sort. I'm using a temp table so that I can exclude the temp variable creation/population from the execution plan I'm about to post.
-- Temp table with sample data
IF OBJECT_ID('tempdb..#Something') IS NOT NULL DROP TABLE #Something;
CREATE TABLE #Something
( TicketNo INT
, ActionDate DATETIME
, OldStatus VARCHAR(50)
, NewStatus VARCHAR(50)
);
INSERT #Something VALUES
(1001, '2014-02-14 10:17:05.000', 'Assigned', 'InProgress')
, (1001, '2014-03-05 02:03:44.000', 'InProgress', 'Reply')
, (1001, '2014-03-11 10:00:14.000', 'Reply', 'Resolved')
, (1002, '2015-03-20 04:44:14.000', 'InProgress', 'Reply')
, (1002, '2015-03-21 05:40:02.000', 'Reply', 'Resolved')
-- TOP (1) Solution
SELECT s.TicketNo, s.ActionDate, s.OldStatus, s.NewStatus,
CycleTimeSeconds = DATEDIFF(SECOND, MyLag.ActionDate, s.ActionDate)
FROM #Something AS s
OUTER APPLY
(
SELECT TOP (1) ActionDate
FROM #Something s2
WHERE s2.TicketNo = s.TicketNo
AND s2.ActionDate < s.ActionDate
ORDER BY s2.ActionDate DESC
) AS MyLag;
-- Using MAX instead of TOP (1) to avoid a DESC sort operation
SELECT s.TicketNo, s.ActionDate, s.OldStatus, s.NewStatus,
CycleTimeSeconds = DATEDIFF(SECOND, MyLag.ActionDate, s.ActionDate)
FROM #Something AS s
CROSS APPLY
(
SELECT ActionDate = MAX(ActionDate)
FROM #Something s2
WHERE s2.TicketNo = s.TicketNo
AND s2.ActionDate < s.ActionDate
) AS MyLag;
Since the subquery is only evaluating one column we can leverage an Aggregate function without a GROUP BY
. Because I'm using an Aggregate (MAX) I'll always get a row back which is why I changed the OUTER APPLY to CROSS APPLY. Nothing wrong with OUTER APPLY but changing it removes scalar operator from this execution plan - no performance gain, just a cleaner execution plan.

There is a very big additional benefit to this approach: It's not only avoiding a sort, we're avoiding a DESCending sort. If there were an index on ActionDate
the optimizer could leverage it to avoid a DESCending sort by performing a *ordered-backward scan. Adding a UNIQUE constraint on ActionDate to the original temp variable (something I don't suggest but it works for this example) run a TOP (1)
query sorted by ActionDate DESC.
declare @Something table
(
TicketNo int
, ActionDate datetime UNIQUE
, OldStatus varchar(50)
, NewStatus varchar(50)
)
insert @Something values
(1001, '2014-02-14 10:17:05.000', 'Assigned', 'InProgress')
, (1001, '2014-03-05 02:03:44.000', 'InProgress', 'Reply')
, (1001, '2014-03-11 10:00:14.000', 'Reply', 'Resolved')
, (1002, '2015-03-20 04:44:14.000', 'InProgress', 'Reply')
, (1002, '2015-03-21 05:40:02.000', 'Reply', 'Resolved')
SELECT TOP (1) ActionDate
FROM @Something AS s2
ORDER BY s2.ActionDate DESC;
Note the backward scan:

Backward scans kill parallelism. Itzik Ben-Gan discusses that here: Avoiding a Sort with Descending Order.
One difference between ordered-forward and ordered-backward scans is
that the former can potentially use parallelism, whereas the latter
doesn’t currently have an implementation with parallelism in the
storage engine
Here APPLY is allowing us a sort-free operation with parallel processing in the optimizer's back pocket if needed. Just more evidence that APPLY is awesome... not that any further evidence was needed ;)