You can do this using external interrupts. Most Arduinos only support this on a limited number of pins though. For full details, see the documentation on
Assuming you're using an Uno, you could do it like this:
attachInterrupt(0, pinChanged, CHANGE);
This will call
pinChanged() whenever a change is detected on external interrupt 0. On the Uno, that corresponds to GPIO pin 2. The external interrupt numbering is different on other boards, so it's important to check the relevant documentation.
There are limitations to this approach though. The custom
pinChanged() function is being used as an Interrupt Service Routine (ISR). That means the rest of the code (everything in
loop()) is temporarily stopped while the call is executing. In order to prevent disrupting any important timing, you should aim to make ISRs as fast as possible.
It's also important to note that no other interrupts will run during your ISR. That means anything relying on interrupts (such as the core
millis() functions) may not work properly inside it.
Lastly, if your ISR needs to change any global variables in the sketch, they should usually be declared as
volatile int someNumber;
That's important because it tells the compiler that the value could change unexpectedly, so it should be careful not to use any out-of-date copies/caches of it.