Doing several things at the same time with functions, objects or interrupts?
An RTOS may be a good option for running a complex application on a
quite powerful Arduino. Many Arduinos (the AVR-based ones), however, are
too limited to run complex applications, and the overhead of an RTOS is
quite significant for them. In this case you end up with two options:

non-blocking programming
interrupts

Whichever is best depends on the specific tasks you are managing.
Non-blocking programming
This is best exemplified by the Blink Without Delay Arduino
tutorial. The general principle is to break down you code into small
non-blocking tasks, and perform each one only when it is the right time
to do so. Breaking down the program logic into a set of non-blocking
tasks is not always completely trivial, but you can use a Finite state
machine, which is a very powerful and general concept for doing so
in a systematic way.
Once all task are non-blocking, the main program loop then looks like
this pseudo-code:
void loop() {
    if (task_1_should_be_performed_now()) {
        perform_task_1();
    }
    if (task_2_should_be_performed_now()) {
        perform_task_2();
    }
    // etc...
}

Most of the time, all conditions are false, and the program swifts
through the tests completing the loop in a very short time.
Occasionally, it performs one of the tasks, which makes the loop longer
but, since the tasks never block, not very long anyway. Typical loop
time is highly dependent on the specifics of your code, but I would
expect it to be no more than one millisecond most of the time.
This is probably the most common pattern for multitasking on the
Arduino. You can encapsulate this logic in functions, objects,
libraries, etc. This is the approach I would recommend provided none of
your tasks is so urgent that it cannot wait for one loop iteration.
Interrupts
Occasionally, you may have a task that is so urgent that it cannot wait
for the next loop iteration. It has to be performed right now. Or maybe
the loop takes a very long time because of a blocking call in a library
you have no control over. In those situations, an interrupt may be the
best option.
Interrupts come with some tricky programming issues, which require some
precautions on the part of the programmer:

they block other interrupts, increasing their latency → interrupt
service routines should be as short as possible

they can cause problems with non-reentrant library functions → those
functions should be avoided in interrupt context

they can interact badly with the optimizer → variables used in both
interrupt context and normal context should be qualified as
volatile

they can cause synchronization issues with the data shared with the
main code → access to that data in normal context should be protected
in critical sections.


For all these reasons, it is best to use interrupts only when needed,
i.e. only when a task cannot wait for the next iteration of loop().