Control flow

Control flow is one of those things that may seem simple at first, but later one may see the messy bits. Or not, because pretty much everything in programming is controversial.

Newbie (or simply less careful) programmers tend to write the code that would only work in perfect conditions at best, since covering all the possibilities seems to be overwhelming and excessive. And then things don't work as they should.

Programming languages and libraries tend to encourage this: usually the most straightforward way to define a program is to define its behaviour in the most desired/expected/perfect conditions, optionally covering the others. In dependently typed languages, there are attempts to assist verification, though not necessarily to make it easier to define correct programs than incorrect ones.

Some methods to handle the situations that are different from the most desirable path in a flowchart:

Built-in exceptions (e.g., Control.Exception)
Whole new control flow, but implicit; no guarantee that exceptions you throw will be handled, no reliable indication of whether a function throws exceptions at all. Rather dangerous, yet handy sometimes, particularly for applications and if an approach is taken to handle errors outside of a program. Although an exit call with an appropriate code and a message in stderr would be more suitable for that.
Except monad (Control.Monad.Except, Control.Monad.Trans.Except)
A less magic version of exceptions, where it is clear which functions throw exceptions, and which exceptions they throw, yet a less powerful one: no way to throw an exception to a different thread, so not useful on its own for tasks such as graceful exit in multithreaded programs.
Return values
… Such as NULL, -1, Maybe a, Either a b, (err, a), Bool. They may force a programmer to do something meaningful with the result, but that only works as long as a return value can't be ignored altogether. When it comes to I/O actions, which are the most error-prone, many of them are executed solely for side effects, and not to get any return value – so a programmer could still easily ignore misbehaviour, though Rust would warn if a Result value is not handled, and GHC with -Wall would warn if it's not discarded explicitly. And as with the Except monad, no way to throw them to a different thread, or do other magic things (though maybe it's rather good than bad).
assert, panic
As seen in C, Rust, Go. Perhaps useful for unrecoverable errors, but normally those shouldn't be common, and it's unnecessary to introduce an additional construct just for those.
Premature return and goto
Similar to exceptions, and criticised for similar reasons. Though as of 2017, there's still no shortage of people who would advocate even the latter.
Event-driven approaches
With these, error conditions are not treated differently from regular ones, and they can lead to rather nice and correct code: one gets encouraged to consider a graph, not just a sequence. These are also good for software extensibility.

An unpleasant thing in languages with built-in exceptions is that those exceptions could pop up just anywhere. Even if one prefers a different approach (such as ExceptT), chances are that other approaches are used in the used libraries, so one ends up dealing with every available kind of branching, possibly wrapping it into something unified along the way. There even are libraries to assist that, such as errors.

Probably one of the reasons of this rather unpleasant situation is that a correct control flow can be represented with a directed graph, a flowchart, while it is tempting/easier to write and see programs as mere sequences of actions; even in dot it's not that handy to describe flowcharts. State machines may be more suitable to describe control flow precisely, while being relatively handy to edit as text. Event-driven architectures seem to be nicer in general, though they may lead to relatively verbose and/or error-prone code.