The main reason for using monads is to "wrap" program state. This allows you to defer your computation (or even do it on another processor, for example). Later you can "unwrap" the result.

There are lots of concrete examples, which often have names. For example "Maybe" is just a container that holds some data or "Nothing" (you can implement "Nothing" with nil/null). When you pass a function to bind, it doesn't run if the container holds "Nothing". It just returns the container with "Nothing". Otherwise the function is run and you can do your computation (returning a container with the result or "Nothing").

Instead of a container that holds data or "Nothing", you can have a container that holds data or an error. This is usually called "Either" (either data or an error), but lately some languages (like Rust) are calling it "Result" which I like better. It's a really simple thing, but dealing with return values that might be errors is a really common problem.

You might be familiar with the technique of dealing with errors using exceptions. With this technique, you always return a value, or you throw an exception (which causes the execution to jump to an error handler). Throwing an exception is an example of a "side effect". The function doesn't always do the same thing -- sometimes it stops half way through and jumps up the stack. One of the main uses of monads is to "wrap" that side effect. You can see that "Either/Result" wraps the return value to allow you deal with the error without having a side effect. You can think of it as a "structured exception" (i.e. you can use normal control structures and you don't need a GOTO). Any time you have a side effect in code, you can actually wrap it in a monad. This makes the code execution much easier to reason about.

As another example, remember I said that monads let you defer computation to a time that is convenient for you. A "Promise" is a monad that wraps a temporal side effect. You return a value that may or may not be filled yet. When you try to run bind on the monad (usually renamed to "then") it blocks the current thread of execution until the value is available.

One of the nice things about monads is because they are just containers, you can easily build monads of monads. For example, imagine a Promise whose contained value is an Either/Result. There are lots of common combinations. To give you some ideas, remember my search result refining example. You can imagine a "set of data" that doesn't contain anything at first. When you "refine" the search, it does a network request that returns a Promise. However, you don't actually have to do the network request at that time. You can just return the Promise. Then you go to the next refinement. This allows you to compose all the refinements and when you are all done then make the network requests. Because your result is just a data structure, you can pass it around to a different part of your program, or even shuffle it off to a different processor before you evaluate it (causing it to do the network requests).

It's this kind of composition that makes monads so powerful. It might be difficult to imagine, but most containers are monads. If you can write a meaningful "bind" function for the container, then it is a monad. Even a closure is a monad. If you partially apply some parameters and return a function, that function is just a container that contains those parameters. You can write "bind" that composes that partially applied function to another function. In this way you can build up large functions. An example of this would be a DSL for validating data -- you can compose all of the functions at program initialisation time and run the result when validating your data.

I think the most important thing to understand is that monads aren't really that complicated. They are containers for which you can write a "bind" function that allows you to chain transformations. It's the consequences of that simple fact that is really amazing. It allows you to put anything in a container -- program state, side effects, executable code, etc and then pass it around like any other data structure. Whenever you think, "This code would be much simpler if I could have X, Y before I do Z, but I don't have them", usually there is a way to put the program state you need for X and Y into a monad and pass it to Z. The result is code that is much easier to reason about.

And just because it's easy to forget: monads are functors. Functors are containers with "map". "map" allows you to transform data in a container and get back a container with that transformed data. monads use "bind" which guarantees that the returned value is exactly the same type as the original monad. This allows you to chain operations. Normally you will use monads for chaining operations and functors for general transformation. But of course they are the same container.