Skip to content
← All posts

Fraction to Recurring Decimal: Long Division with Cycle Detection

4 min read
leetcodeproblemmediummathhash-map

Fraction to Recurring Decimal is LeetCode problem 166. Given two integers numerator and denominator, return the fraction as a string. If the fractional part repeats, enclose the repeating portion in parentheses.

Examples:

  • 1 / 2 = "0.5"
  • 2 / 1 = "2"
  • 4 / 333 = "0.(012)"
  • 1 / 3 = "0.(3)"

The key insight is that this is long division, the same process you learned in school, and repeating decimals happen when the same remainder appears twice.

4 / 333 = 0.(012)RemainderRem * 10Digit440040 / 333 = 0, rem 40404001400 / 333 = 1, rem 67676702670 / 333 = 2, rem 4cycle!New remainder = 4 (seen at position 0). Digits 0, 1, 2 repeat.Result: "0." + "(" + "012" + ")" = "0.(012)"
Long division of 4/333. When remainder 4 reappears, the digits produced since the first occurrence of 4 form the repeating block.

Approach: simulate long division, detect cycles with a hash map

Think about what happens when you do long division by hand. You take the remainder, multiply by 10, divide by the denominator, and write down the quotient digit. Then the new remainder becomes the input for the next step. If you ever see a remainder you have seen before, the digits between the two occurrences of that remainder will repeat forever.

The algorithm:

  1. Handle the sign. If exactly one of numerator or denominator is negative, prepend "-".
  2. Compute the integer part with floor division.
  3. If the remainder is zero, return the integer part as a string.
  4. Otherwise, start producing decimal digits. Before each digit, record the current remainder and its position in the result string. If the remainder already exists in the map, insert "(" at the stored position and append ")" to the end.
  5. For each step: multiply the remainder by 10, compute the next digit (remainder // denominator), and update the remainder (remainder % denominator).
  6. If the remainder becomes zero, the decimal terminates.

Python solution

def fractionToDecimal(numerator: int, denominator: int) -> str:
    if numerator == 0:
        return "0"

    result = []

    if (numerator < 0) != (denominator < 0):
        result.append("-")

    num = abs(numerator)
    den = abs(denominator)

    result.append(str(num // den))
    remainder = num % den

    if remainder == 0:
        return "".join(result)

    result.append(".")
    seen = {}

    while remainder != 0:
        if remainder in seen:
            result.insert(seen[remainder], "(")
            result.append(")")
            break
        seen[remainder] = len(result)
        remainder *= 10
        result.append(str(remainder // den))
        remainder %= den

    return "".join(result)

Walkthrough: 4 / 333

Let's trace through the algorithm step by step to see how it produces "0.(012)".

Tracing fractionToDecimal(4, 333):

Step 0: Integer part

remainder =4
remainder * 10 =0
0 // 333 =0remainder 0 % 333 =4
seen map:{}
result so far:"0."

4 // 333 = 0 with remainder 4. The integer part is "0". Since the remainder is not zero, add a decimal point and continue.

Step 1: First decimal digit

remainder =4
remainder * 10 =40
40 // 333 =0remainder 40 % 333 =40
seen map:{4: pos 0}
result so far:"0.0"

Record remainder 4 at position 0 in the seen map. Multiply 4 by 10 to get 40. 40 // 333 = 0 with remainder 40. Append digit "0" to result.

Step 2: Second decimal digit

remainder =40
remainder * 10 =400
400 // 333 =1remainder 400 % 333 =67
seen map:{4: pos 0, 40: pos 1}
result so far:"0.01"

Record remainder 40 at position 1. Multiply 40 by 10 to get 400. 400 // 333 = 1 with remainder 67. Append digit "1".

Step 3: Third decimal digit (cycle detected)

remainder =67
remainder * 10 =670
670 // 333 =2remainder 670 % 333 =4
seen map:{4: pos 0, 40: pos 1, 67: pos 2}
result so far:"0.012"
Remainder 4 already in seen! Insert "(" at position 0 and append ")".

Record remainder 67 at position 2. Multiply 67 by 10 to get 670. 670 // 333 = 2 with remainder 4. Remainder 4 is already in the seen map at position 0. Insert "(" at position 0 and append ")" to get "0.(012)".

Final result: "0.(012)"

The remainder 4 repeated after producing digits "012". Those three digits form the repeating cycle, enclosed in parentheses.

The seen map is the entire mechanism that powers cycle detection here. Each remainder maps to a position in the result list. When a remainder repeats, you know exactly where to insert the opening parenthesis.

Complexity

Time: O(d) where d is the number of distinct decimal digits produced before the result terminates or a cycle is detected. In the worst case, d can be as large as the denominator (since there are at most denominator - 1 distinct nonzero remainders). Each step does constant work.

Space: O(d) for the seen map and the result list, both of which store at most d entries.

Building blocks

This problem decomposes into two reusable pieces you will encounter in other problems.

1. Long division simulation

The core loop of multiplying the remainder by 10, dividing by the denominator, and tracking the new remainder is a direct simulation of manual long division. This same pattern appears anytime you need to produce decimal digits from integer arithmetic without using floating point.

while remainder != 0:
    remainder *= 10
    digit = remainder // denominator
    remainder %= denominator

2. Cycle detection via seen remainders

Storing each remainder with its position and checking for repeats is the same "have I seen this state before?" pattern from Happy Number and Linked List Cycle. The only difference is what you are tracking: here it is a division remainder, there it is a sequence value or a node pointer.

seen = {}
while state is not None:
    if state in seen:
        # cycle detected at seen[state]
        break
    seen[state] = current_position
    state = next_state(state)

On CodeBricks, you drill each building block separately. When they show up combined in a problem like this, you recognize both pieces instantly.

Edge cases

  • Zero numerator: 0 / anything = "0". Return immediately.
  • Exact division (no repeating part): 1 / 2 = "0.5". The remainder reaches zero and the loop ends naturally, no parentheses needed.
  • Negative numbers: -1 / 2 = "-0.5", 1 / -2 = "-0.5", -1 / -2 = "0.5". Use XOR on the signs to decide whether to prepend "-". Work with absolute values after that.
  • Large numbers: The denominator determines the maximum cycle length. With denominator = 1,000,000,007, the repeating block could be up to a billion digits. Python handles arbitrary-precision integers natively, but be aware of this in languages with fixed-width types.
  • Numerator larger than denominator: 7 / 2 = "3.5". The integer part is nonzero. The algorithm handles this in step 2 without any special case.

By the pigeonhole principle, a fraction with denominator d can have at most d - 1 distinct nonzero remainders. So the repeating cycle is at most d - 1 digits long. For 1/7 = 0.(142857), the cycle has exactly 6 digits, the maximum for denominator 7.

From understanding to recall

Reading this walkthrough gives you the logic. But when you face a similar problem in an interview, you need to produce the code from scratch. The two building blocks here, long division simulation and cycle detection with a hash map, appear together in this problem but also separately in many others.

Spaced repetition makes those building blocks automatic. CodeBricks drills you on each piece at increasing intervals. After a few review sessions, "track remainders in a map to find cycles" becomes second nature. You stop thinking about the mechanics and start thinking about the problem.

Related posts

  • Happy Number - Cycle detection with a hash set applied to digit-square sequences, the same "have I seen this state?" pattern used here
  • Linked List Cycle - Floyd's tortoise and hare for cycle detection in linked lists, another form of the cycle detection building block