Ruby Flip Flop: What It Is and When to Use It

Apart from being a synonym to a sandal, a ruby flip-flop refers to a system that comprises two alternating (on/off) states that one can switch between. This can be pretty helpful when you want to loop through arrays and capture contiguous subsets that lie between specific bounds (you’ll understand more of what I mean as you read along). However, not many programming languages have leveraged a mechanism like this.

Let’s dive in and learn more about what this operator is, how it works, its different types, what it offers, and where it makes sense to use it. Here’s an outline of what we’ll be covering so you can easily navigate or skip ahead in the guide –

What Is the Ruby Flip Flop Operator? 

Ruby’s flip-flop is a range operator (‘..’ or ‘...’) that allows you to program a switchable on/off condition-based control flow for your application. It is one of the many Perl concepts that made its way into the Ruby language.

A flip-flop is used between two conditions inside a loop with the following syntax - 

my_array.each do |x| # or any other loop
  if (condition1)..(condition2) # two conditions with a flip-flop
    # do something (body)
  end
end

Let’s discuss how this flip-flop mechanism broadly works step-by-step.

  1. Your loop runs.
  2. The program waits for the first condition of the flip-flop to be true before it executes the underlying body the first time.
  3. When the condition is true, the (internal) state of the flip-flop activates. Subsequently, the body executes each time (the outer loop runs) until the second condition evaluates to true.
  4. When the second condition returns true, the flip-flop’s state becomes false immediately after the body executes.
  5. As a result, the body does not execute in subsequent iterations until the first condition becomes true again.

We’ll take an elementary example to see how this functions –

# flipflop_example.rb
[0,1,2,3,4].each do |x| # or any other loop
    if (x==1)..(x==3)
        puts "Flip-flop active: #{x}"
    else
        puts "Flip-flop inactive: #{x}"
    end
end

Output:

$ ruby flipflop_example.rb
Flip-flop inactive: 0

Flip-flop active: 1

Flip-flop active: 2

Flip-flop active: 3

Flip-flop inactive: 4

As our loop traverses the array when the first condition (x==1) satisfies, the flip-flop activates, and the underlying body executes. Now, until the second condition (x==3) returns true (and toggles our flip-flop state to off), the state remains active, and the body executes at each iteration. 

Essentially, the operation boils down to the first condition checking when to start the flip-flop and accordingly running its body, whereas the second condition checks when to pause or stop.

Note that even after the second condition satisfies, the body will execute once before the flip-flop deactivates. Once deactivated, the first condition needs to be true again for the state to toggle and the body to run.

Below is a schematic diagram of how each iteration in the loop unfolds in our previous example. Here, C1 and C2 are the two conditionals around the flip-flop operator, and the on/off switch represents the internal state of the flip-flop.

undefined
Ruby flip-flop example explained.

Now let’s write the above example code without using any flip-flops –

# flipflop_without_flipflop.rb
state = false # extra state variable
[0,1,2,3,4].each do |x| # or any other loop
    if (x==1) or state == true # condition C1 and state check
        state = true
        puts "Flip-flop active: #{x}"
    elsif state == false
        puts "Flip-flop inactive: #{x}"
    end
    if (x==3) # condition C2 and state check
        state = false
    end
end

It is easy to observe how bloated and complex this gets. You need to maintain an extra variable for tracking the flip-flop’s state. Once understood, the concept of flip-flops is relatively straightforward – your code (usually) runs from the point the first condition satisfies until the second one does. This simple piece of logic shouldn’t take too many lines to implement if you think about it.

There are a few nuances in the workings of flip-flops across their (two) different types (that we’ll explore in a while). For example, what if both conditions are true at the same instance? What if the first condition satisfies for two iterations in a row before the second one? Etc. However, this example above captures the essence of the operator. As the name suggests, it follows a flip-flop mechanism that allows you to toggle the running of a block of code based on two conditionals.

It is important to note that the range operator works as a flip-flop operator only in the context of an if, unless, or a ternary statement. Without these, it works as a mere range operator (that will be erroneous if used with two conditionals).

# flipflop_example.rb
[0,1,2,1,1,3,4].each do |x| # or any other loop
    (x==1)..(x==3) # without an if condition
end

Output:

flipflop_example.rb:3:in `block in <flipflop_example>': bad value for range (ArgumentError)
from flipflop_example.rb:2:in `each'
from flipflop_example.rb:2:in `<main>'

Is Flip Flop Deprecated in Ruby?

Flip-flops, expectedly, received a mixed bag of reactions – some developers liked it and found the functionality quite helpful. In contrast, others advocated removing it based on the unnecessary complexity it introduced and its rare usage. Here’s an issue on the Ruby forum dating back almost ten years proposing the discontinuation of flip-flops.

After much consideration, the Ruby team decided that flip-flop usage would (temporarily) raise a warning in Ruby v2.6 (shown below) and perhaps even be deprecated eventually.

$ irb     
> RUBY_VERSION # print ruby version
=> "2.6.3"
> [0,1,2,3,4,5].each do |x|
>     if (x==1) .. (x==4)
>         puts x
>     end
> end
(irb):3: warning: flip-flop is deprecated
1
2
3

4
=> [0, 1, 2, 3, 4, 5]

However, it had to come back in version 2.7 onwards upon popular demand. Therefore, to run flip-flop code and follow along with our examples, you will need a Ruby installation with version > v2.7 (or < v2.6).

Two Types of Ruby Flip Flops

Because the flip-flop is a range operator, it can take two forms (based on the two-dot and three-dot ranges) and thus work in two slightly different ways.

In conventional Ruby ranges, the one with two dots (..) includes both the ends of the range (first and last elements). The range with three-dots, however, excludes the upper limit. However, how this difference manifests in a flip-flop is dissimilar. In a flip-flop operator, the difference between the two-dot and three-dot range lies primarily in the execution (checking) of the two conditionals used with the operator. Let’s look at each of these in more depth.

Ruby Flip Flop with a Two-Dot Range (..)

In the introductory example of flip-flops, we used the one with a two-dot range. This operator allows both the conditions in the if statement to execute during one iteration. This will be noticeable when we take an example where the second condition is also true along with the first one. Let’s take our previous simple example and have both the conditions be true at one instant – 

# flipflop_example.rb
[0,1,2,3,4].each do |x|
    if (x==1)..(x==1) # if true .. true
        puts "Flip-flop active: #{x}"
    else
        puts "Flip-flop inactive: #{x}"
    end
end

In this case, when our loop reaches ‘ 1 ’, the first if condition passes and activates the flip-flop. However, immediately after, the second condition also passes, which deactivates the operator’s state. As a result, the enclosed block runs only once, whenever the iterator evaluates to 1, and only then. 

Output:

$ ruby flipflop_example.rb
Flip-flop inactive: 0

Flip-flop active: 1

Flip-flop inactive: 2

Flip-flop inactive: 3

Flip-flop inactive: 4

Therefore, the output here is just ‘1’.

This evaluation of both conditions is what primarily distinguishes it from the three-dot range flip-flop operator. We’ll dive into the other variant next.

Before that, one crucial thing to note about both conditions’ checking in the two-dot operator is that the second condition evaluation happens only when the first condition evaluates (or recently evaluated) to true. This means that if our first condition never turns true, the second condition is never checked.

# flipflop_example.rb
[0,1,2,3,4].each do |x|
    if (x==-1)..(x==1) # first condition never true
        puts "Flip-flop active: #{x}"
    else
        puts "Flip-flop inactive: #{x}"
    end
end

Output: 

$ ruby flipflop_example.rb
Flip-flop inactive: 0
Flip-flop inactive: 1
Flip-flop inactive: 2
Flip-flop inactive: 3
Flip-flop inactive: 4

As you can see, the flip-flop never activates.

Ruby Flip Flop with a Three-Dot Range (...)

The three-dot range flip-flop operator only allows the checking of one if condition for one loop iteration. This doesn’t affect the output of the (more common) case where both the conditions aren’t true simultaneously. Let’s look at the same example but with different if conditions – 

# flipflop_example.rb
[0,1,2,3,4].each do |x|
    if (x==1)...(x==3) # three-dot operator
        puts "Flip-flop active: #{x}"
    else
        puts "Flip-flop inactive: #{x}"
    end
end

Output:

$ ruby flipflop_example.rb
Flip-flop inactive: 0

Flip-flop active: 1

Flip-flop active: 2

Flip-flop active: 3

Flip-flop inactive: 4

The output and computation here wouldn’t have changed even if we used the two-dot operator because the two conditions here would never be the same for any iteration.

However, in the case where both the conditions could be true, the three-dot operator would never let the second one evaluate. As a result, it would keep the flip-flop’s state active and allow the block to execute for subsequent iterations. We’ll use the same example as usual –

# flipflop_example.rb
[0,1,2,3,4].each do |x|
    if (x==1)...(x==1) # three-dot operator; if true … true
        puts "Flip-flop active: #{x}"
    else
        puts "Flip-flop inactive: #{x}"
    end
end

Output:

$ ruby flipflop_example.rb
Flip-flop inactive: 0

Flip-flop active: 1

Flip-flop active: 2

Flip-flop active: 3

Flip-flop active: 4

Here, in the second iteration, when the first condition passes, the flip-flop activates and runs the code block without checking what the second condition returns. As a result, the flip-flop stays activated throughout. This is in contrast to the two-dot operator, where the second condition, if true, would deactivate the flip-flop right away.

However, once the first condition returns true and your flip-flop is activated, only the second condition is evaluated in subsequent iterations, completely ignoring the first one.

Ruby Flip Flop Example

So far, we have understood the nuances of the flip-flop using a very trivial example.

Before we wrap up, let’s use a more practical example for showcasing where flip-flops can be helpful.

One of the popular use cases of flip-flops is for text processing and manipulation. This example will create a dummy HTML parser that can selectively extract elements of our choice (e.g., bullet points inside <li> tags) and their content from HTML files.

For the sake of brevity and to focus on how flip-flops can be helpful, we will assume that we already have the basic, cleaned-up HTML string (without significant nesting and with elements separated by lines) to start with.

Let’s take a dummy web page like this one –

Sample web page

Below is our Ruby script, where we’ll try to parse this page’s HTML and extract the <li> bullet points.

# flipflop_example_2.rb
html = "<html>  # html web page string
    <head></head>
    <body>
        <h2>
            Lorem ipsum bullet points
        </h2>
        <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p>
        <ul>
            <li>
                Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
            </li>
            <li>
                Ut enim ad minim veniam quis nostrud exercitation ullamco, laboris nisi ut aliquip ex ea commodo consequat
            </li>
            <span>(consectetur adipiscing elit)</span>
            <li>
                Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
            </li>
        </ul>
    </body>
</html>"   

html_array = html.split(/\n+/) # decomposing multi-line string into array
html_array = html_array.map(&:strip) # removing leading and trailing white space

puts "Stripped HTML array elements -> \n\n"

puts html_array # displaying the HTML array we’ll traverse

puts "\n––––––––––––––––––––––––\n\n"
puts "All bullet points (<li> elements) with content – \n\n"



html_array.each do |elem|
    if (elem=='<li>') .. (elem=='</li>')  # flip-flop
        puts elem
    end
end

Here, we start with a cleaned-up HTML string, split it into an array (by line), remove the leading and trailing whitespaces (using strip), print the array (to see what it finally looks like), and then use our flip-flop operator to extract the bullet point elements (enclosed by <li> and </li>) from the list. Let’s see what the output looks like.

Output:

$ ruby flipflop_example_2.rb
Stripped HTML array elements ->

<html>
<head></head>
<body>
<h2>
Lorem ipsum bullet points
</h2>
<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p>
<ul>
<li>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</li>
<li>
Ut enim ad minim veniam quis nostrud exercitation ullamco, laboris nisi ut aliquip ex ea commodo consequat
</li>
<span>(consectetur adipiscing elit)</span>
<li>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</li>
</ul>
</body>
</html>

––––––––––––––––––––––––

All bullet points (<li> elements) with content –

<li>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</li>
<li>
Ut enim ad minim veniam quis nostrud exercitation ullamco, laboris nisi ut aliquip ex ea commodo consequat
</li>
<li>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</li>

This functionality can easily be extrapolated to parse text files and extract segments marked by specific beginning and end symbols. 

When Should You Use Ruby Flip Flops?

It is safe to say that using flip-flops makes your code a little difficult to follow; however, it does save you a bunch of if statements and an extra state variable at the cost of comprehension. It is a tradeoff between understandability and conciseness. 

Because of how less common flip-flops are for general-purpose code, you want to make sure that the rest of the developers on your team have some understanding of what a flip-flop operator is before you go ahead and use these all over your code. If everyone’s on the same page, then it might be worth trying this; but if not, the readability and easy comprehension offered by a bunch of if-statements is always the safer option.

While it might seem a little unintuitive at first, once you have flip-flops for long enough, in different contexts, you start to get the hang of it and can appreciate the syntactic sugar and the ease of usage they offer. As a result, these operators are commonly used for text manipulation and text processing applications.

Happy Coding!

Now that you have this new tool in your Ruby development toolkit think about where you could save a bunch of lines in your code by using this fancy operator that most people haven’t even heard about. Consider sharing this with a friend and trying it in a new project to test your understanding and become better at it.

If you consider yourself a Rubyian (or whatever they’re calling pro Ruby developers these days), here are some of the other popular Ruby posts on our blog that you should consider checking out –

And if you are looking to boost your application’s performance, make sure to check out Scout’s APM tool for live alerts about bottlenecks, memory bloat, leaks, slow database queries, real-time performance insights, and much more. Get started with a 14-day free trial (no credit card needed)! Also, check out our blog for more software programming and web development content.