It mixes handling of many different service types, including daemons, so-called agents (which is just like a daemon except per-user and with muddled semantics, since they can all be per-login, per-user, GUI session or non-GUI session, but at the same time are the only category where direct user interaction is permitted), Mach services, XPC services and others. It also wrongly equivocates process types with scheduling policies, making for the really awkward and overlapping semantics of "Background", "Interactive", "Standard" and "Adaptive". Further complicating this is the LaunchOnlyOnce flag, for one-shot jobs. It's all inconsistent
Using its socket activation means you need to explicitly opt in to your daemon using liblaunch. It's not a generic mechanism like UCSPI is, for instance.
Whereas daemontools uses the actor model/Erlang philosophy of "let it crash" and restarting to known good states until dependencies are met, and systemd is based on a complicated dependency resolution/ordering mechanism... launchd is in this weird middle ground of neither. It instead expects OS services to resolve their sequences through IPC, but at the same time provides really racy and primitive ordering techniques through the KeepAlive key, evidently because users and sysadmins needed something. The result are inherently racy file system namespace mutexes like PathState, very limited network synchronization through NetworkState and a really easy to abuse poor man's dependency system through OtherJobEnabled (which actually violates launchd's own philosophy of IPC over dependencies).
In spite of its attempts to do load balancing and scheduling, the interfaces for doing so are mixed. You have standard rlimits, but also ambiguous ones like LowPriorityIO and LimitLoad<wildcard>, latter ones depending on sysctl variables.
It reportedly has weird interactions with cron because of it holding timers while sleeping: http://hea-www.harvard.edu/~fine/OSX/launchd_cron.html
WaitForDebugger is an odd key to have. Implies to me that launchd breaks standard ptrace(2) semantics.
And of course, XML plists.
In summary, even though launchd has good ideas, the entire architecture is just a mess of layering violations, semantic discrepancies, incorrect coupling and insufficient interfaces that make it really awkward to use. It tries to be the one-stop management interface for just about every unit of CPU time on the whole damned system, all of it in PID1 at that, and so naturally its abstractions become clumsy. Different tasks call for different systems to run and schedule them.