That is way too much for me to put into a single comment, but the most common approach is to split your program into "pure" and "impure" parts and then model all of your interactions with the real world in a way where the "pure" part of your program becomes an "interpreter" that deals with the pure representations of real-world events.
A common pattern for doing this (but by no means the only one) is using Free Monads.
I guess it's a little easier with OOP, but it isn't very clear to me how "polymorphism" helps me open a garage door, or even achieves it's stated goals. I guess I can at least understand that now I can open any door by calling door.open() or something. Of course it gets much worse when you graduate and people tell you OPP is overhyped, there's too much abstraction here. Then you think "I thought the abstraction was good". Don't just create a garage door, create a door factory! Then you realize you don't have a good defense, and that you don't understand as much as you thought. You were never taught to design programs pragmatically. You just have to figure that part out on your own.
I can understand how mutable state causes problems and would be a good thing to avoid where possible. There's really not much needed to convince even a moderately experienced programmer, of the power of pure functions. It doesn't feel that far off from the OOP example.
Have something nullable? Stick it inside `Maybe` and map it.
Have a side effect in `IO` and want to manipulate the result? Map it.
Following that, look at monadic binding. It's basically flat mapping.
From here you can start to see how programs can be composed in languages like Haskell. You have these foundational typeclasses and you sort of just connect all your functions together with them until you're left with one big expression in `main` which is your application entrypoint.
But I’d say in general that FP is mostly just an alternative to OOP, maybe even leas than that, not a complete paradigm for writing complex programs. At least if you use a mainstream language, then FP is probably something you do pretty locally.
The only caveat is in I/O mechanisms. An IO monad is one way to safely manage IO, but it is not the only way.
Although I must say, my examples are actual instances where I tried to put the shallow learning I had done about FP and failed spectacularly at doing so. It was no doubt due to my ignorance and lack of experience with FP, but maybe also there is a part of me not working with problems that lend themselves easily to a FP approach...