Currently Pipeline builds will almost always start running immediately upon entering the queue (unless you disable concurrent builds altogether), since the Groovy code itself does not require a heavyweight executor.
Unfortunately this makes it impossible to reproduce a sometimes desirable behavior of freestyle projects, which is queue coalescing: cf. QueueAction, FoldableAction, and ScheduleResult.Existing. The main use case is running only as many concurrent builds as available executors permit, coalescing SCM commits where necessary, while tracking Cause.
I would suggest this be implemented as an option to the milestone step. For example, if your script is written like
and build #1 is running in node while #2 is running but waiting for an executor, if the same job is added to the queue again, rather than immediately starting build #3, Existing should be returned from schedule2 and any FoldableAction in #2 retroactively updated. Once an executor frees up and #2 makes it to the second milestone, new builds can be scheduled again.
Not sure if this is implementable using current core APIs. QueueDecisionHandler can only return Refused; we would like an option to return Existing instead (or simply take over the ScheduleResult).