For loop w/o delay

Here's one adapted from Millis() instead of delay and loop() instead of for-loop - #21 by DaveX

You could add an external switch instead of the boolean constant to toggle between blocking an non-blocking modes.

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN,OUTPUT);
}

void loop() {
  const bool doBlocking = true; // choose between blocking or non-blocking code

  const unsigned long quickInterval = 250; //
  const unsigned long slowInterval = 1000;

  if (doBlocking == true) { // use delay() and for(;;)
    for (int i = 0 ; i < 4 ; ++i) {
      toggleLed();
      Serial.print(i);
      Serial.print(" ");
      delay(quickInterval);
    }
    Serial.println("-- blocking");
    delay(slowInterval);

  }  else { // unblocked loop() by:
    // * replacing delays with millis() edge-detectors/state machines
    // * replacing for(;;) loop with a counter state machine

    // state variables to persist values past the end of loop()'s scope
    static int i = 0; // digit to print
    static unsigned long lastDigitMs = -quickInterval; // backdate by -quickInterval for immediate operation
    static unsigned long lastLineMs = 0; // state variable for edge-detecting slow delay

    unsigned long now = millis(); // remember the loop time

    // edge detecting state machine {i,now-las}
    if (i < 4 && now - lastDigitMs >= quickInterval) { // edge-detect interval change
      lastDigitMs = now; // measure digit delay from last digit print
      toggleLed();
      Serial.print(i);
      Serial.print(" ");
      ++i;
      lastLineMs = now; // measure line ending delay from last print time
    }
    // edge-detecting state machine
    if (i >= 4  && now - lastLineMs >= slowInterval) {
      Serial.println( "-- nonblocking");
      i = 0; // reset the count
    }
  }
}

void toggleLed(void){
  digitalWrite(LED_BUILTIN,digitalRead(LED_BUILTIN)==HIGH?LOW:HIGH);
}