Okay, this is a bit embarrassing to admit, but almost everyone gets stuck on this point when they start programming, and nobody explains it well. You write a method, it "does the math", but when you try to use the result outside the method, nothing is there. And then the question hits you: "but didn't it calculate something? Where did that value go?"

Let's break this down.

What "returning a value" means

When you call a method and store the result in a variable, like this:

Decimal totalWithTax = calculateTax(1000);

after this line, totalWithTax holds a real number that you can use in the rest of your code. The method calculated something and handed it back to whoever called it. That is what returning means.

According to the Apex Developer Guide, every method must declare a return type, and that type is exactly what the method promises to give back when it finishes.

Void is a different story

Now look at this method:

public void logMessage(String message) {
    System.debug(message);
}

You call it like this:

logMessage('Process started');

Notice: there is nothing on the left side receiving a value. There is no String result = logMessage(...), because there is no result to store.

The official documentation is clear about this: use void when a method does not return a value. The method performs an action, in this case writing to the log, but after calling it, you are not left with any new data in your hands.

"But it did something, so it kind of returned something, right?"

This is the tricky part. Performing an action and returning a value are two separate things.

Writing a log entry is an action, it has a real effect, it gets recorded, but it does not produce a piece of data for you to use later in your code. Calculating a tax amount, on the other hand, is a calculation that produces a result, and that result needs to travel back to whoever called the method, so it can be shown on screen, saved to a field, and so on.

Can't I just use System.debug inside the method?

No. And this is the part that confuses people the most.

public void calculateTax(Decimal baseAmount) {
    Decimal tax = baseAmount * 0.1;
    System.debug(tax); // you see the value here, but only here
}

While the code is running inside the method, the variable tax really does exist, holding the calculated value. But according to the language's concept of variable scope, a variable declared inside a method only exists inside that method. When the method finishes, it disappears.

System.debug() only prints the value to the log, so you can look at it while testing or debugging. It does not carry that value anywhere else in your code. Whoever called the method still has no access to it.

Return is the "transport"

public Decimal calculateTax(Decimal baseAmount) {
    Decimal tax = baseAmount * 0.1;
    return tax;
}

return is the mechanism that takes the value from inside the method, where it exists only temporarily, and carries it outside, where whoever called the method can store it in a variable and keep using it.

Without return, the value is born and dies inside the method. Nobody outside ever gets close to it.

Quick summary

voidreturn Type
Performs an actionYesCan also do it
Returns a valueNoYes
Can store the result in a variableNoYes
Examplelogging, sending a notificationcalculating a value, fetching data
A simple question to decide which one to use:

after calling this method, do I need to store something it calculated? If yes, use return with the right type. If not, use void.

Official sources