Theoretical max RPM tachometer

num blades is a float because I want rpm not to be rounded like when dividing by an integer. This way I don't have to do any convert it.

It doesn't need to be.

    float RPM = passesPerSecond * 60.0 / numBlades;

Divide a float by an int, and you still get a float.

What's the difference with using a volatile (sorry if it's a dumb question)?

The volatile declaration tells the compiler that the value can change at any time. Otherwise, in loop(), the value may be cached, and changes to the variable won't be seen.

In the ISR I count the number of times something passes infront if the sensor, and records the time from the first pass to the most recent pass.

Only if the "most recent pass" is pass number 10 or more. At very slow speeds, your RPM values will be wrong.

The whole idea of recording time only after 10 passes is flawed. Record the time EVERY time.

Thank you for your input to improve my code, but it is working

For some definition of working, I guess.