Programming Katas: Best Practices for Turning Repeated Exercises into Deliberate Practice, Technical Learning, and Real Code Improvement

Learn how to use programming katas for deliberate practice, better tests, safer refactoring, and continuous code improvement.

Imagine the scene: you set aside an evening to “practice coding.” You choose FizzBuzz, open your editor, solve it in a few minutes, run the tests—when there are any—and think: “Okay, I know how to do this.” The following week, you try another kata, then another, then another. The list grows, but the feeling of technical progress does not keep up.

representing the idea of deliberate practice in programming
representing the idea of deliberate practice in programming

This is a common sign that practice has turned into automatic execution.

Programming katas are not about solving a difficult problem once. They work best when we treat small exercises as deliberate practice: repeating with intention, observing decisions, receiving feedback, and adjusting the way we write code.

The value lies less in “I finished” and more in “What did I notice about my tests, names, refactorings, and design choices?”

This guide is for anyone who has heard of katas but wants to practice in a more consistent, productive way connected to real code improvement.

TL;DR

  • Katas are short exercises repeated with intention, not intelligence tests or speed rankings.
  • The best practice starts before the editor: define a specific skill to practice.
  • Simple exercises help because they reduce domain load and make technical decisions more visible.
  • TDD can be an excellent tool, but it should not become an empty ritual.
  • Reviewing the process is just as important as getting all the tests to pass.
  • Pair programming, group review, and community increase the quality of feedback.
  • A well-practiced kata ends with a clear observation to guide the next session.

Programming katas: what they are—and what they are not

A programming kata is a short exercise, usually with simple rules, repeated with intention, observation, and adjustment. The idea is close to deliberate practice: you train specific movements in a controlled environment to improve your technique.

For this reason, a kata should not be treated as a single attempt to “get the solution right.” If you solved it once and never looked at it again, you may have completed a useful exercise, but you have not yet explored the kata’s full potential.

It is also useful to distinguish katas from other practices:

  • Algorithm challenges and competitions: they can be good for logical reasoning, but they often prioritize time, ranking, or algorithmic performance. Katas, in this context, prioritize programming technique, feedback, and code evolution.
  • Real project tasks: real systems involve domain knowledge, legacy code, integration, product concerns, communication, and organizational constraints. Katas do not replace them.
  • Passive tutorials: watching someone solve a problem can help, but practice happens when you make decisions, make mistakes, test hypotheses, and review your work.
  • Copying code without understanding it: repeating someone else’s solution without reflecting on names, tests, structure, and trade-offs becomes guided typing.

Simple exercises are useful precisely because they reduce domain noise. In FizzBuzz, for example, you do not need to understand tax rules, distributed authentication, or a temperamental asynchronous queue at three in the morning. This creates room to observe details: are the tests clear? Do the names help? Does the structure make change easier? Does the refactoring preserve behavior?

If you want a starting point on the idea of daily practice, it is worth reading the article Code Katas: pratique programação diariamente. To connect this practice with continuous learning and care for code, O que é Software Craftsmanship provides important context.

One caution: a kata is not proof of seniority. Solving Bowling Game from memory does not automatically turn anyone into a senior developer, just as knowing how to play a scale does not mean someone can master an entire show.

It helps, but it is fundamentals training. Another caution: isolated practice does not replace maintaining real systems. It complements that work.

What a good kata session should develop

Before opening the editor, define what you want to practice. It sounds simple, but it completely changes the session.

Instead of saying:

“I’m going to do FizzBuzz.”

Prefer something like:

“I’m going to practice expressive names and small refactoring steps with FizzBuzz.”

Or:

“I’m going to use String Calculator to practice incremental TDD, writing one small test at a time.”

This change shifts the focus from the exercise as a “problem to solve” to the skill you want to develop.

A good kata session usually includes several elements of deliberate practice:

  • limited scope, so it fits into a short session;
  • a specific goal, such as improving tests, refactoring more effectively, or communicating decisions;
  • fast feedback, through tests, review, or pairing;
  • repetition with conscious change, rather than repetition on autopilot;
  • review of both the result and the process, to decide on the next experiment.

In practice, katas can help develop skills such as:

  • writing small, clear tests;
  • breaking behavior down into smaller parts;
  • identifying duplication;
  • refactoring safely;
  • handling edge cases;
  • explaining technical decisions;
  • noticing when an abstraction helps or gets in the way;
  • improving the names of functions, methods, classes, and variables.

On the other hand, there is a limit. Very simple exercises make it easier to focus on technique, but after a few repetitions they may stop challenging aspects of design.

At that point, it may be better to change the constraint, practice in another language, solve the kata in pairs, or choose another kata with richer rules.

How to choose a kata that fits your practice goal

Choosing a kata should not start with the question “Which one is the most famous?” but rather with “What skill do I want to practice right now?”

Start with the type of skill you want to practice

Some criteria are especially helpful:

  • the problem should be small enough to fit into one session;
  • the rules need to be clear;
  • there should be room for incremental evolution;
  • the exercise should allow for testing and refactoring;
  • the complexity needs to match the goal of the practice.

For example:

  • FizzBuzz is useful for initial testing cycles, conditional rules, simple names, and conditional refactoring.
  • String Calculator helps practice incremental TDD, requirement evolution, validation, and error cases.
  • Roman Numerals explores conversion rules, boundary tests, and decisions about exceptions.
  • Bowling Game is interesting for decomposing rules and observing test-driven design.
  • Game of Life introduces domain rules, composition, and the evolution of a solution beyond the trivial case.

Notice that the value is not in building a huge catalog. The value lies in choosing the right exercise for the type of technical tension you want to observe.

If you have never practiced TDD, String Calculator may be a better choice than Game of Life. If you already know FizzBuzz well, the session may need a new constraint:

“I’m not going to allow duplicated conditionals.”

Or:

“I’m going to do ping-pong pairing.”

Or:

“I’m going to record every refactoring I make.”

The kata is intentionally small. The goal is to use this controlled space to see your decisions more clearly.

Choose a few exercises and go deeper

A common trap is switching katas whenever difficulty appears. Someone jumps from FizzBuzz to Roman Numerals, then to Bowling Game, then to Game of Life, always looking for novelty. This can be fun, but it can also hide learning.

Repeating the same exercise is part of the method. The first execution reveals the problem. The following ones reveal your habits.

You may notice, for example, that you:

  • always write tests that are too large;
  • take too long to rename something whose intent has already changed;
  • create abstractions too early;
  • avoid refactoring when the test suite does not provide confidence;
  • solve the rule but leave the code difficult to explain.

On the other hand, repeating too much without a new goal becomes automatic. If you have solved FizzBuzz ten times in the same way, perhaps you are no longer training; perhaps you are just stretching your fingers on the keyboard.

To renew the learning, vary a constraint, language, approach, or partner.

A practical method for running a kata in 30 to 60 minutes

showing a kata’s feedback cycle
showing a kata’s feedback cycle

A kata session does not need to take an entire afternoon. In many cases, 30 to 60 well-structured minutes teach more than several hours of scattered attempts.

Before coding: define an intention and a constraint

Start by choosing an observable goal—something you can evaluate at the end.

Examples:

  • “I want to write tests with names that explain behavior.”
  • “I want to refactor without breaking the suite.”
  • “I want to avoid long methods.”
  • “I want to practice small commits.”
  • “I want to explain every decision aloud during pairing.”
  • “I want to identify duplication before creating abstractions.”

Then choose a productive constraint. Constraints are not meant to complicate things for the sake of it; they are meant to increase attention on a specific aspect.

For example:

“By the end, I want to be able to explain why each test exists and remove duplication without breaking the suite.”

This intention already creates a better quality criterion than “finish quickly.”

During implementation: use short feedback cycles

A simple flow for the session is:

  1. understand one rule;
  2. write a small test;
  3. implement the minimum necessary;
  4. refactor;
  5. review the decision;
  6. repeat.

This flow works well with TDD, but TDD should be viewed here as a tool, not a religious obligation. It is useful when it helps you move in small steps, receive fast feedback, and protect refactoring.

If TDD is still new to you, the article Teste Unitário: do zero ao TDD can serve as further reading before a session focused on this technique.

Some cautions during implementation:

  • Avoid writing all the tests before implementing. It may seem organized, but you lose some of the learning generated by small cycles.
  • Avoid “facade TDD”: implementing everything first and writing tests afterward merely to validate what has already been done.
  • Do not anticipate the entire architecture just because you already know the exercise.

This last point is especially important. With a well-known kata, it is tempting to start with the “final” design. This may save time, but it reduces the practice of incremental discovery.

If the goal of the session is to practice feedback-driven evolution, too many shortcuts get in the way.

When you finish: review the process, not just the result

When all the tests pass, the session is not over yet.

Set aside the final few minutes to answer questions such as:

  • Which decision made the code simpler?
  • Where did I hesitate?
  • Which tests were difficult to write?
  • What duplication appeared?
  • What coupling emerged?
  • What became clearer after the refactoring?
  • What would I change in the next repetition?

A short record already helps. It can be something as simple as:

“Today I realized that my tests started by describing implementation rather than behavior. In the next session, I’ll focus on clearer test names.”

This observation is valuable. It turns an isolated execution into continuous learning.

How to use TDD and refactoring without turning the kata into a ritual

TDD and refactoring work very well with katas, but there is a catch: turning technique into choreography.

The goal of TDD is not to repeat “red, green, refactor” as if it were a secret club password. The goal is to obtain feedback, guide decisions, and keep the code evolvable.

Likewise, refactoring is not “changing things because they look ugly.” It means improving the structure without changing behavior.

The relationship between tests, design, and refactoring can be understood this way:

  • tests make changes safer;
  • refactoring improves structure while preserving behavior;
  • short cycles reveal design problems early;
  • design improves when you pay attention to the friction involved in adding new rules.

In String Calculator, for example, you can start by returning a result for an empty input. Then move on to one number. Next, two numbers. Only after that introduce separators, multiple values, or additional rules. This small evolution forces you to make decisions with frequent feedback.

In FizzBuzz, you can observe when repeated conditions start to become a problem. Perhaps the order of the checks makes it difficult to add a new rule. Perhaps names such as result or value are too generic to explain intent. Perhaps extracting a function helps—or perhaps it is excessive at that point.

This is where trade-offs come in:

  • Creating abstractions too early can make a simple problem artificially complex.
  • Refactoring purely for aesthetic reasons can reduce clarity.
  • Not refactoring anything can hide duplication and fragile decisions.
  • TDD can help a lot, but not every session needs to start with TDD.

A valid alternative is to create an exploratory initial solution and then write tests to characterize the behavior before refactoring. This can be useful when your goal is to compare design styles or understand the problem first.

The important thing is to make the intention explicit.

Learn more through review, pair programming, and community

illustrating pair practice and collaborative review
illustrating pair practice and collaborative review

Katas become richer when they stop being a solitary practice. Comparing solutions should not be a competition to decide “who did it better.” The goal is to see decisions, alternatives, and costs that went unnoticed.

Two pieces of code can solve Roman Numerals correctly and still reveal very different strategies:

  • one solution may prioritize conversion tables;
  • another may use decomposition by decimal place;
  • one may have clearer boundary tests;
  • another may make it easier to add new rules;
  • one may be easier to read but less flexible;
  • another may be more extensible but too complex for the current problem.

Useful formats for practicing in pairs or groups include:

  • driver-navigator: one person types while the other observes direction, risks, and next steps; then they switch;
  • ping-pong pairing: one person writes a test, the other makes it pass, and the roles alternate;
  • post-kata code review: people solve the exercise individually and then review the solutions together.

For example: two people solve Roman Numerals separately for 30 minutes. Then they discuss names, boundary tests, structure, and how easy it is to add rules. The conversation will probably teach more than simply looking at your own finished solution.

A short retrospective also helps:

  • What did we learn?
  • Where did we disagree?
  • Which decision had a hidden cost?
  • What alternative do we want to try in the next session?

The important thing here is to prevent review from becoming ego defense. It is not a trial, ranking, or speed test. It is technical practice. The code is the study material, not an extension of its author’s identity.

Pairing also involves a trade-off: feedback is usually richer, but individual keyboard time decreases. That is why alternating solo and collaborative sessions can serve different goals.

If you want to practice with other people, join the SCCB community and follow technical meetups, conversations, and activities.

Avoid mechanical repetition: create a growth routine

connecting katas with continuous improvement
connecting katas with continuous improvement

A sustainable routine is better than a burst of enthusiasm followed by abandonment. One short weekly session, or two 30-minute sessions, can already create continuity—as long as each session has a goal.

A simple progression cycle can be:

  1. solve a familiar kata with a focus on understanding;
  2. repeat it using a specific technique;
  3. review it with another person;
  4. change a constraint or language;
  5. record what transferred to your real work.

This last step is important. Katas do not replace real projects, but they can improve the movements you use in them.

For example: after practicing boundary tests with Roman Numerals, you may bring the same care to a validation rule in a real system. After practicing refactoring with String Calculator, it may become easier to make a small, safe improvement to legacy code. After practicing explanations during pair programming, your next code review may become more objective.

Even so, keep the contexts separate:

  • katas train technical movements in a controlled environment;
  • real projects involve domain knowledge, integration, maintenance, communication, product concerns, and business constraints.

Another common mistake is measuring progress only by execution time or the number of completed exercises. “I solved 20 katas” says little if you do not know what changed in the way you think and write code.

A better question would be:

“What can I do today with more clarity, confidence, or intention than I could before?”

A well-practiced kata does not end when all the tests pass. It ends when you leave with a clear observation to guide the next session.

Next Steps

To get started without overcomplicating things:

  1. Choose a simple kata, such as FizzBuzz or String Calculator.
  2. Define a specific intention: tests, names, refactoring, edge cases, or communication.
  3. Practice for 30 to 60 minutes.
  4. Set aside the final few minutes to review the process.
  5. Record one observation and one hypothesis for the next repetition.
  6. When possible, review the solution with another person.

If you want to practice with a community, see SCCB’s upcoming events and join the technical conversations.

Did you enjoy this article?

Share it with your friends and help spread knowledge!