How does a Hashing algorithm always result in a digest in a fixed size? This question revolves around the Hashing Algorithm attribute that the resulting digest is always the same size (also known as Fixed Width). For example, whether you hash the word "hello", or 10gb file, MD5 will always return a 128 bit digest.

My question is, how do hashing algorithms always result in a constant length digest ?

The only operation I can think of is the MOD / remainder operation that regardless of the starting value, after doing a MOD operation using modulus X, the result is always a value between 0 and (X-1). But I am specifically curious if there are other operations that also serve in reducing (or increasing, I guess) the digest size to get to the desire digest length.

note: I am not interested specifically in how MD5 does it, but in what operations exist that any hashing algorithm might use to always ensure the same size digest
Basic Building Blocks of a Hash Function

The part of a hash function that leads to the digest always being the same size regardless of input length is called the compression function. The compression function is then linked whmacith a domain extender which extends the compression function to allow it to map across any length of input.  

Construction From Block Ciphers

I am going to use the construction of the WHIRLPOOL hash function as an example of how a compression function and domain extender works.

Setup  

It is very simple to construct a hash function from a block cipher. The first step in a block cipher algorithm would be to split the data to be hashed into blocks the same number of bits as the input to the block cipher being used. If needed padding is added to the input to make it a multiple of the key size. No difference here from normal block cipher operation.

Compression Function with Domain Extension

There is a lot of variation between different designs here but basically what they all perform though is some means of feeding each block into the chosen cipher and some combination of XOR or some other operation on the previous rounds output, the current rounds output, the input, or all of the above. For example the following is the compression function of WHIRLPOOL.  

For the following let:
$$
\begin{align}
    m_i\:&amp;=\:\mbox{Message or Input}\\
    H_i\:&amp;=\:\mbox{Output}\\
    W\:&amp;=\:\mbox{Block Cipher}\\
    g\:&amp;=\:\mbox{Bit Mapping Function}
\end{align}
$$
  

To construct a new hash function it would be possible to simply plug a new block cipher into $W$ and the size of input and output would change with the input and output new cipher. There are many other forms of compression function structures with different properties also, and they mainly consist of changing when, what, and in what order things are combined. I would put a link here but reputation limit, so wikipidia's compression functions article has more on this.

The domain extension which allows it to operate over any amount of data is simply how the output feeds back into the cipher as the key as each subsequent block is encrypted replacing the key schedule. It will only output the result after all blocks have been fed through the block cipher effectively limiting output to the size of the ciphers output no matter the input size.

Other Constructions

There are also hash functions that are based on mathematical problems that are hard to solve and also stream ciphers. This is really a topic that an entire book could be dedicated to but they all use the basic structure of a compression function and domain extender. More info on the general construction of hash functions, compression functions, and domain extenders can be found here: https://eprint.iacr.org/2012/322.pdf