I'll cover a few typical uses of `Block`, neither of which is possible using `Module` or `With`.

**Temporarily removing definitions**

When you do 

 Block[ {a = x}, ... ]

the original definition of `a` is effectively replaced by whatever new definition is given in the first argument of `Block`, for the duration of the evaluation of `Block` only. If we give no definition, `Block` temporarily "undefines" whatever was passed to it as first argument. This works even for built-ins:

 Block[ {Print}, Print["boo!"]; ]

prints nothing.

**Temporarily redefining symbols**

`Block` is also commonly used to temporarily change the value of system variables. Take for example this simple recursive implementation of a list maximum function:

 max[{a_}] := a
 max[list_List] := 
 With[{m = max@Rest[list]}, If[First[list] > m, First[list], m]]

For a long list it fails because the value of `$RecursionLimit` is too low. But we can increase `$RecursionLimit` only temporarily using `Block`:

 Block[{$RecursionLimit = 1000}, max[RandomInteger[10000, 300]]]

**Implementing functions that localize their arguments**

Functions like `Table`, `Sum`, `Plot`, `FindRoot`, `NIntegrate`, etc. use `Block` to localize their variable.

Here's a possible implementation of a `table` function which works like `Table`:

 Clear[table]
 SetAttributes[table, HoldAll]
 table[expr_, {x_, min_, max_, step_}] :=
 Block[{x},
 x = min;
 Reap[While[x <= max,
 Sow[expr];
 x += step
 ]][[2, 1]]
 ]

We can use it just like `Table`:

 table[i, {i, 1, 100, 4}]
 
 (*
 ==> {1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, \
 61, 65, 69, 73, 77, 81, 85, 89, 93, 97}
 *)

Using `Block` made sure that this function will work even if `i` has a value globally.

-------

**``Internal`InheritedBlock``**

Here it's also worth mentioning ``Internal`InheritedBlock``. Like with `Block`, any changes made to the local symbols of `InheritedBlock` are lost when it finishes evaluating. However, unlike `Block`, it keeps the original definition of the localized symbols too.

This is useful for modifying existing (or built-in) functions temporarily. Here's an example to illustrate:

`Print` does not have the `HoldAll` attribute:

 Print[1 + 1]

 (* ==> 2 *)

We can assign `HoldAll` to `Print` temporarily:

 Internal`InheritedBlock[
 {Print},
 SetAttributes[Print, HoldAll];
 Print[1 + 1];
 ]

 (* ==> 1+1 *)

As soon as `InheritedBlock` exists, this behaviour is reverted:

 Print[1 + 1]

 (* ==> 2 *)