Maximizing Pair Programming Flow
Using the four conditions of Flow to improve pairing effectiveness
In my last post, I shared one professional’s opinion on the role of Flow in Software Engineering. To kick off this post, I’ll share another professional’s opinion.
Woody Zuill is known for discovering Mob Programming (or simply, “mobbing”). In one of his talks, he details its relation to Flow. First, he distinguishes Lean Flow, Individual Flow, and Team Flow as distinct concepts. Lean Flow is thought of as the process of minimizing work-in-progress (also referred to as inventory) through the reduction of queue times caused by blockers and dependencies (for example, waiting on a designer to approve a UI revision that’s uncovered during implementation).
Individual and Team Flow, on the other hand, stem from Mihaly Csikszentmihalyi’s definition of Flow that we had covered at length in the prior post. See the excerpt below from the talk in question:
Woody asserts that his team is indeed able to get into a flow state. If Flow is possible when mobbing, could it be achievable when pairing as well? And do these flow states feel different from the classical flow states achieved when working alone?
It turns out that there are common characteristics around Flow that Csikszentmihalyi explains in depth. These hold true no matter how varied the activity or domain — whether it’s a parent teaching their toddler about the world, or a surgeon performing an operation on a patient (both of these are examples taken from Csikszentmihalyi’s book).
Let’s revisit the four conditions of Flow that were covered in the last post, and relate them to the pair programming experience.
Four Conditions of Flow
The four conditions of Flow are what enable a flow state to be both entered into and sustained. Practising pair programming effectively is not easy — in prior posts, I have described at various ways in which pair programming can go awry and how adhering to prescribed pairing styles such as ping-pong and strong-style can help maintain the effectiveness of pairing. Here, I’ll describe how Csikszentmihalyi’s model for Flow can help improve the effectiveness of pair programming, regardless of the particular pairing style being used.
A Challenge Requiring Skills
Challenging ourselves while solo coding is typically straightforward. Product teams that employ a scatter-gather strategy for completing the work have this figured out. Team members pick work at the right challenge for themselves off the backlog. For example, less experienced coders will leave the harder tickets for the more senior members of the team to tackle.
But how does this work with a team that pairs continuously? What if a junior who is anchoring a relatively straightforward ticket is paired up with a senior? How does the senior ensure that they’re being sufficiently challenged?
One way to achieve this is for the senior to take a coaching stance. For example, the pair may start in Driver-Navigator with the junior driving. The senior then takes copious notes not just about, for example, “how might we improve this codebase” but also, “what feedback can I provide to this junior engineer to help accelerate advancement in their skills?”
At the next pairing break, the Navigator shares their notes and the pair discuss. Should they try a more ambitious refactoring and see where it goes? Even if it’s thrown away, the junior will have gained valuable experience in how a comprehension refactoring is performed. Or, the senior shares feedback on how the junior can be more effective. This could be sharing keyboard shortcuts and strategies for learning them quickly, or tips on how to change code more incrementally, AKA “baby steps”.
In summary, ensuring the work is challenging for a pair is a much taller order than for a soloist. Nevertheless it is not only possible but critical if the team recognizes the importance of up-skilling its more junior members.
Conditions for Focus
At first blush, “conditions for focus” seems easy to achieve in the age of remote work. If we are fortunate enough to be able to work from a secluded spot in our home, there’s no manager to come by and tap us on the shoulder to ask us for that “five minute favour”. We are arguably in much better control of stymieing distractions such as these. Even with plenty of virtual means of being reached, we can easily switch them off — as examples, we can simply close our email client or snooze our Slack notifications.
Paradoxically, the challenge in achieving “conditions for focus” can arise when we plan for longer pairing sessions. Namely, the challenge of actually having the pairing session happen in the first place. For a few reasons, I have noticed that blocking off anything longer than one hour for pairing dramatically drops the likelihood that the session happens. For one, there is the challenge of finding calendar availability with your pairing partner. The other challenge — unspoken yet insidious — is the lack of appetite for long pairing sessions. The reality is that most find the thought of a multi-hour pairing session daunting and debilitating. And that’s assuming that both pairing participants plan to challenge themselves at the session. If that prerequisite isn’t met, it’s easy for the senior to use the excuse that they simply don’t have that much time in their day available — they need time to “be productive”.
To overcome this, I typically aim for one-hour pairing sessions. If it’s possible to book a longer pairing session, I plan for multiple 25-minute sessions with 5-minute breaks in between each, making that plan clear to my pairing partner.1 These 5-minutes are used to either check-out completely or perform a short retro. In the past, if we both wanted to keep going (i.e. skip the break) then we would. Now, I insist on the retro instead. If we want to keep going, that means things are going well. What are these things, exactly? How can we “turn up the good”?
“Conditions for focus” means having the conditions for achievable and sustainable focus. That means being able to agree with our colleague on frequent pairing sessions of reasonable lengths. It also means having the option to take breaks frequently, and to reflect on what’s working and what isn’t.
Traditionally, when we think of goals in a professional setting we think big. Increase customer engagement. Ship that release. Reduce our change failure rate. In the context of Flow, however, we think of goals on a much smaller scale. When it’s practising a musical instrument, a goal may be to play a piece of music all the way through without making a mistake. Oftentimes, a better goal means going even smaller — play a single bar2 such that every note is played clearly and in tune, while keeping the rhythm of the piece.
How does this translate to coding? Firstly, specification by example and BDD allow us to break down a user story into a series of scenarios. Each scenario is comprised of one or more examples. Each example can be codified into an automated test. Writing a failing test that expresses behaviour of unimplemented code is the first goal. The next goal is to make that test pass. The goal after that is to improve the code we’ve written so that it can be easily maintained and extended. By laying out these small, Flow-oriented goals, we’ve just described the red-green-refactor workflow of TDD.
Feedback is what informs us of our progress towards achieving our goals. Good feedback is timely and actionable. When one plays a note on a musical instrument, the feedback is instantaneous as the note is heard immediately. If the performer has a “good ear”, they will recognize whether the note is flat or sharp. In this way, they clearly know in which direction to adjust the pitch.
In a similar way, effectively practising TDD means reading and interpreting the error messages of the executed test. As long as we move incrementally (AKA “baby steps”), the error messages will be actionable. If our unit tests are well-written and well-maintained, they execute quickly — therefore, the feedback they offer will be timely as well.
The smaller we make our goals, the clearer they become. Small goals also make continuous feedback achievable. Effective feedback is timely and actionable. In software development, this is enabled through TDD. In addition, pair programming adds a layer of support that keeps us on track while continuously improving upon the effectiveness of our goal-setting and feedback collection.
Not only is Flow possible when pairing, there are often plenty of opportunities for improvement. We can uncover these opportunities when we reflect on and apply Csikszentmihalyi’s four conditions of Flow. Further, we see how marrying pair programming with TDD allows us to set clear goals that are supported by highly effective feedback loops — namely, feedback that is both timely and actionable.
I find that Csikszentmihalyi’s mental model around Flow holds a high degree of relevance to the practice of Software Engineering and pair programming in particular. I hope to explore this further in future posts.
Having said that, I have created the above poll to learn about what you want to read more about. Please take a moment to read through the options and pick the one that jumps out at you.