I think that the topic of short-circuiting is an under-appreciated one. In short, short-circuiting is simply ending an evaluation the instant it becomes false:

if(2 < 1 && 1 == 1) { /* stuff */ } if(2 > 1 || 1 == 1) { /* stuff */ } 1 2 3 if ( 2 < 1 && 1 == 1 ) { /* stuff */ } if ( 2 > 1 || 1 == 1 ) { /* stuff */ }

In this example, the second condition doesn’t get evaluated in either case.

In the first example, the compiler sees that both conditions have to be true in order for the complete evaluation to be true. Since the first condition is false, then logically, the whole thing is false so the second condition is ignored.

In the second example, the compiler sees that either condition can be true for the complete evaluation to be true. Since the first condition is true, then logically, the entire thing is true so the second condition is ignored.

Ok great, this is Programming 101 stuff, right?

Well of course it is. I think the majority of us understand that && and || are short-circuit operators and & and | are bit-wise operators, but based on what I see in several code bases, I’m not positive that many of us give enough credit to how simple and powerful the short circuit concept can be in day to day coding. Here’s why I just said “concept” instead of “operator” :

By thinking in terms of short-circuiting, we can potentially reduce load by making more efficient, logical statements. In other words, let’s not make our program work harder than it needs to. Here’s another short circuit example:

foreach(Person person in People) { if(person.Age >= 18) { person.Image = _db.GetImage(person.Id); } if(person.Age >= 21) { person.CanDrink = true; } } 1 2 3 4 5 6 7 8 9 10 11 12 foreach ( Person person in People ) { if ( person . Age >= 18 ) { person . Image = _db . GetImage ( person . Id ) ; } if ( person . Age >= 21 ) { person . CanDrink = true ; } }

The problem with this loop other than it’s questionable practicality (this is just for illustration) is that this loop will evaluate every single person over 18 at least twice: are they over 18, then are they over 21. What if we try to make this a little more efficient by skipping the entire block where possible. Logically, if the person being evaluated is under 18, then none of this logic would be run, right? Maybe something like this:

foreach(Person person in People) { if(person.Age < 18) continue; person.Image = _db.GetImage(person.Id); if(person.Age >= 21) { person.CanDrink = true; } } 1 2 3 4 5 6 7 8 9 10 11 foreach ( Person person in People ) { if ( person . Age < 18 ) continue ; person . Image = _db . GetImage ( person . Id ) ; if ( person . Age >= 21 ) { person . CanDrink = true ; } }

Again, the simplicity of this is just for illustration, but what we’ve done is short-circuited the loop. Since you have to be over the age of 18 in order to be over the age of 21, we’ve concluded that if you’re not over the age of 18, then this logic does not apply to you at all. Next person, please. The continue keyword tells the loop to skip the rest of the body and move on to the next iteration.

Prime Numbers

Short circuit operators and short circuiting loops isn’t all that is short-circuiting. I tend to think of short-circuiting as any logic where you interrupt the logic when a condition fails to apply to that logic.

So, I think we’ve all done the prime number quiz at least once in our careers. Here’s what I see most juniors come up with:

Example 1

static void Main(string[] args) { // output all prime numbers between 1 and 100 for (var i = 2; i <= 100; i++) { var isPrime = true; for (var j = 2; j < i; j++) { if (i%j == 0) isPrime = false; } if(isPrime) Console.WriteLine("{0} is a prime number.", i); } Console.ReadKey(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static void Main ( string [ ] args ) { // output all prime numbers between 1 and 100 for ( var i = 2 ; i <= 100 ; i ++ ) { var isPrime = true ; for ( var j = 2 ; j < i ; j ++ ) { if ( i % j == 0 ) isPrime = false ; } if ( isPrime ) Console . WriteLine ( "{0} is a prime number." , i ) ; } Console . ReadKey ( ) ; }

Now, this makes logical sense, especially if you haven’t done this before and/or haven’t had an opportunity to optimize it. Basically you take your number and then you test every number between 1 and your number to see if they divide evenly. If any of them divide evenly, then your number is not a prime number.

The problem with this is that it will test every number in the number set which is 2 through 100. If the range were 2 through 1 million, then it would test every number in that set as well. There’s no short-circuit. Let’s take a different approach? How about first, we break it into something a bit more manageable, and then apply some short circuiting.

Example 2

static void Main(string[] args) { // output all prime numbers between 1 and 100 foreach (var number in Enumerable.Range(1, 100)) { if(IsPrime(number)) Console.WriteLine("{0} is a prime number.", number); } Console.ReadKey(); } private static bool IsPrime(int number) { if (number < 2) { return false; } // short circuit if (number == 2) { return true; } // short circuit if (number % 2 == 0) { return false; } // short circuit for (var d = 3; d < number; d += 2) { if (number % d == 0) return false; // short circuit } return true; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static void Main ( string [ ] args ) { // output all prime numbers between 1 and 100 foreach ( var number in Enumerable . Range ( 1 , 100 ) ) { if ( IsPrime ( number ) ) Console . WriteLine ( "{0} is a prime number." , number ) ; } Console . ReadKey ( ) ; } private static bool IsPrime ( int number ) { if ( number < 2 ) { return false ; } // short circuit if ( number == 2 ) { return true ; } // short circuit if ( number % 2 == 0 ) { return false ; } // short circuit for ( var d = 3 ; d < number ; d += 2 ) { if ( number % d == 0 ) return false ; // short circuit } return true ; }

In the IsPrime function, we’ve set 3 easy short-circuits. We know that numbers less than 1 are not prime. Next, we know that 2 is the only even prime number. Then we also know that if the number divides evenly by 2, it is not a prime number. So by calling these things out early in the process, we can eliminate the need to run the loop, saving us some valuable cycles. Now if we get past all of these short circuits, then we can run through the loop as before and test the remaining numbers. If we wanted to take it further, we can apply some math knowledge to further reduce the number set size:

Example 3

static void Main(string[] args) { // output all prime numbers between 1 and 100 foreach (var number in Enumerable.Range(3, 100)) { if(IsPrime(number)) Console.WriteLine("{0} is a prime number.", number); } Console.ReadKey(); } private static bool IsPrime(int number) { if (number < 2) { return false; } // short circuit if (number == 2) { return true; } // short circuit if (number % 2 == 0) { return false; } // short circuit for (var d = 3; d * d <= number; d += 2) { if (number % d == 0) return false; // short circuit } return true; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static void Main ( string [ ] args ) { // output all prime numbers between 1 and 100 foreach ( var number in Enumerable . Range ( 3 , 100 ) ) { if ( IsPrime ( number ) ) Console . WriteLine ( "{0} is a prime number." , number ) ; } Console . ReadKey ( ) ; } private static bool IsPrime ( int number ) { if ( number < 2 ) { return false ; } // short circuit if ( number == 2 ) { return true ; } // short circuit if ( number % 2 == 0 ) { return false ; } // short circuit for ( var d = 3 ; d * d <= number ; d += 2 ) { if ( number % d == 0 ) return false ; // short circuit } return true ; }

By changing our loop condition to d * d <= number we’re eliminating the squares of the number (and everything above it) which would have been basically testing the same number multiple times. This isn’t a short-circuit per say, but I think it goes along with the “eliminate unnecessary evaluations” theme.

Conclusion

So basically, we need to remind ourselves to cut out the fat, especially in iterative logic. Any time we can eliminate unnecessary evaluations, now matter how seemingly insignificant, we could potentially gain mountains of processing time. Just for fun, I went and bench-marked these three examples by processing 0 through 1 million (0xF4240) and here’s what I ended up with:

Example 1: 1,579,187 ms (26 minutes!)

Example 2: 64,797 ms

Example 3a: 3,507 ms

Example 3b: (without the first 3 short-circuits): 7,364 ms