Skip to the solution
HardBackend14 min read

What is Python 3.12's free-threaded (no-GIL) build and what changes?

94 views

The problem

Question presented to candidate: "You have probably heard that Python is getting rid of the GIL. What exactly is the free-threaded build, which version brought it, and what changes for someone writing threaded Python code or maintaining a C extension?"

What a strong answer should cover:

  • The GIL lets only one thread run Python bytecode at a time; the free-threaded build (PEP 703) is a separate CPython build, configured with --disable-gil, in which threads run Python code truly in parallel on several cores.
  • Version facts: it is not in 3.12 (3.12 brought immortal objects, PEP 683, and a per-interpreter GIL, PEP 684). It first shipped as experimental in 3.13 (python3.13t), and became officially supported, still optional, in 3.14 (PEP 779). The default download still has a GIL.
  • What changed inside: biased reference counting, immortal objects, deferred reference counting, the mimalloc allocator, and per-object locks so built-in list/dict stay internally consistent.
  • What it means for your code: race conditions that were rare become common, so += on shared state needs a Lock exactly as before; single-threaded code ran about 40% slower in 3.13 and the 3.14 target is at most 10%.
  • C extensions must declare free-threading support (Py_mod_gil); importing one that does not re-enables the GIL with a warning. Detect the build with sysconfig.get_config_var("Py_GIL_DISABLED") and the runtime state with sys._is_gil_enabled() (3.13+); control it with PYTHON_GIL or -X gil.

Clarifying questions expected:

  • Do you mean what is in the default CPython download, or the separate free-threaded build?

Code / implementation expected: Optional β€” detect the build, and show that shared counters still need a lock.

  • gil
  • free-threaded
  • python3.12

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

14 min read Β· 12 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

PythonWhy the GIL never made += atomic, and how to detect the build
PythonThread code that is correct on both builds
# Code that is correct on BOTH builds: the default (GIL) build and the
# free-threaded build (python3.13t / python3.14t). Verified here on the
# default 3.13 build; the free-threaded run was not available to us.
import sys
import sysconfig
import threading
from concurrent.futures import ThreadPoolExecutor

free = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
print("free-threaded build:", free)
print("GIL enabled at runtime:", sys._is_gil_enabled())


def crunch(n):
    # Pure-Python CPU work: serialised by the GIL, parallel on 3.13t
    total = 0
    for i in range(n):
        total += i * i % 7
    return total


with ThreadPoolExecutor(max_workers=4) as pool:
    results = list(pool.map(crunch, [200_000] * 4))
print("same answer from every thread:", len(set(results)) == 1, results[0])

# Shared mutable state: guard read-modify-write with a Lock.
counter = 0
lock = threading.Lock()


def bump(times):
    global counter
    for _ in range(times):
        with lock:
            counter += 1


threads = [threading.Thread(target=bump, args=(50_000,)) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print("locked counter:", counter)

# Built-in containers stay internally consistent in both builds
# (the free-threaded build gives them per-object locks), but a
# check-then-act sequence across two calls still needs your own lock.
shared = []


def append_many(tag):
    for i in range(10_000):
        shared.append((tag, i))


threads = [threading.Thread(target=append_many, args=(k,)) for k in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print("appends kept:", len(shared))

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 139 of 139 in Python

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Python questions
End of the topic

That was the last Python question.

Pick another topic