2025, Oct 21 00:00

How to Fix Python Turtle Fill Artifacts: One begin_fill/end_fill, Continuous Outlines, and Reliable Arcs

Learn why Python Turtle fills fail on arc shapes and how to fix them: draw a continuous closed path, use one begin_fill/end_fill call, and keep arcs consistent

Filling irregular Turtle shapes made of arcs can be surprisingly tricky. If the outline isn’t perfectly continuous or the fill is started and stopped in the wrong places, you get striped artifacts or empty regions instead of a solid fill. Below is a practical walkthrough of why this happens and how to fix it using Turtle’s standard fill workflow.

Problem setup

The outline is built from arc fragments to mimic organic borders. The drawing routine computes points along an oval and moves the pen accordingly. The goal is to fill the entire closed figure, but calling begin_fill and end_fill around these arcs produced an odd pattern instead of a solid fill.

Code that demonstrates the issue

The following function generates an arc by stepping angle values and placing the turtle at computed coordinates. It uses math and turtle directly. The logic below is representative of the setup that leads to the unexpected fill result.

import turtle
import math

def draw_arc(ax, by, deg_start=None, deg_end=None, pen_obj=None):
    if pen_obj is None:
        pen_obj = turtle.Turtle()
    ox = pen_obj.pos()[0]
    oy = pen_obj.pos()[1]
    try:
        adj_start = math.radians(deg_start) * 0.875 * (180 / math.pi)
        adj_end = math.radians(deg_end) * 0.875 * (180 / math.pi)
    except:
        adj_start = 0
        adj_end = 315
    for k in range(int(adj_start), int(adj_end) + 1):
        if k == int(adj_start):
            pen_obj.up()
        else:
            pen_obj.down()
        pen_obj.setposition(ox + ax * math.cos(k / 50), oy + by * math.sin(k / 50))

Why the fill breaks

The most common culprit is a gap in the path. Filling in Turtle requires a closed, continuous polygonal path. If the pen lifts at the wrong time or the last point doesn’t precisely meet the first, the filler can’t determine the interior reliably and you get unexpected patterns.

There is also a consistency problem around angles. Mixing degrees and radians leads to slightly misplaced points, which in turn can open up microscopic gaps along the outline. Here the step variable is treated like degrees in one place and used as radians in another, which is enough to break a fill even if the outline looks visually closed.

Finally, calling begin_fill and end_fill around each small fragment is counterproductive. The fill should start once before drawing the entire outline and end once after the outline is complete.

The fix: one fill operation, continuous outline, and consistent arcs

The simplest path to a robust fill is to draw the shape as one continuous path and call begin_fill only once before you start and end_fill once after you complete the closed figure. Using turtle.circle to build arcs makes continuity straightforward and avoids angle unit mix-ups.

import turtle as tr

def ripple(radius=20, sweep=45):
    tr.circle(radius, sweep)
    tr.circle(-radius, sweep)

def shape_outline(repeat_count=2, radius=20, sweep=90):
    for _ in range(4):
        for _ in range(repeat_count):
            ripple(radius, sweep)
        tr.left(90)

tr.fillcolor("red")
tr.begin_fill()
shape_outline(repeat_count=3)
tr.end_fill()
tr.mainloop()

This draws a wavy rectangle-like polygon, ensures the path is continuous, and fills it cleanly. The key points are that the figure is closed and that begin_fill and end_fill are called only once around the entire outline, not per arc.

Variant with rounded corners and rotation

If you need rounded corners and rotation control, you can still keep a single fill pass around the full path. This version draws the same wavy sides, adds rounded corners, and allows the sweep to be provided as a command-line argument.

import turtle as tr
import sys

def ripple(radius=20, sweep=45):
    tr.circle(radius, sweep)
    tr.circle(-radius, sweep)

def shape_outline(repeat_count=2, radius=20, sweep=90, rounded=True):
    for _ in range(4):
        for _ in range(repeat_count):
            ripple(radius, sweep)
        if rounded:
            tr.circle(radius, 90)
        else:
            tr.left(90)

if len(sys.argv) > 1:
    SWEEP = int(sys.argv[1])
else:
    SWEEP = 90

tr.fillcolor("red")
tr.begin_fill()
tr.right(SWEEP // 2)
shape_outline(repeat_count=3, sweep=SWEEP)
tr.left(SWEEP // 2)
tr.end_fill()
tr.mainloop()

Why this matters for Turtle graphics

Fill quality depends on geometry. Even a pixel-wide discontinuity lets the fill leak out or fail to compute. Keeping a single uninterrupted outline and using the fill API in one shot makes the behavior predictable. Avoiding a mix of radians and degrees in coordinate generation prevents subtle misalignments that are almost invisible but deadly for polygon fill. Choosing turtle.circle for arcs provides a consistent, engine-native path that stays continuous between segments.

Takeaways

Use begin_fill once before drawing the entire shape and end_fill once after the path is fully closed. Ensure the outline has no gaps, including tiny ones caused by angle unit inconsistencies. Prefer Turtle’s own arc primitives to maintain continuity when possible. With these adjustments, complex silhouettes made of repeated arc elements fill cleanly and reliably.

The article is based on a question from StackOverflow by ونداد شاهدی and an answer by furas.