Turing Reductions

Gregory M. Kapfhammer

October 13, 2025

Learning objectives

Learning Objectives for Theoretical Machines

  • CS-204-1: Use both intuitive analysis and theoretical proof techniques to correctly distinguish between problems that are tractable, intractable, and uncomputable.
  • CS-204-2: Correctly use one or more variants of the Turing machine (TM) abstraction to both describe and analyze the solution to a computational problem.
  • CS-204-3: Correctly use one or more variants of the finite statement machine (FSM) abstraction to describe and analyze the solution to a computational problem.
  • CS-204-4: Use a formal proof technique to correctly classify a problem according to whether or not it is in the P, NP, NP-Hard, and/or NP-Complete complexity class(es).
  • CS-204-5: Apply insights from theoretical proofs concerning the limits of either program feasibility or complexity to the implementation of both correct and efficient real-world Python programs.

Reduction techniques, like Turing reduction, aid when proving that a problem is “hard”!

  • “Reduction for hardness” helps with learning objective CS-204-4, in which you learn how to prove a problem is uncomputable or otherwise “hard”. Okay, let’s dive in!

Prove computational problem is hard

  • Main goal: Prove a problem is uncomputable
    • We suspect a problem cannot be solved by any algorithm
    • Need rigorous mathematical proof to establish this fact
    • Cannot just say “it seems hard” or “I can’t think of a solution”
  • Two proof approaches to try
    • Contradiction proof: Prove uncomputability by contradiction
    • Reduction proof: Constructively transform a known hard problem \(F\) to a new problem \(G\), thereby showing that new problem \(G\) is at least as hard as known problem \(F\)
    • Let’s explore how to perform a reduction proof!

Why do reductions work?

  • We have a problem \(F\) that is already proven to be uncomputable
  • We want to prove a new problem \(G\) is also uncomputable
  • Reduction from \(F\) to \(G\) means we can solve \(F\) using a solution for \(G\)
  • Steps: Proceed by contradiction to prove reduction approach works
    • Assume \(G\) is computable
    • Construct a reduction from \(F\) to \(G\)
    • This means that \(F\) is also computable
    • This contradicts the fact that \(F\) is uncomputable
    • This means the initial assumption was incorrect
    • We can therefore conclude that \(G\) is uncomputable

Example of Turing reduction concepts

Reduction as Python source code

import utils; from utils import rf
from lastDigitIsEven import lastDigitIsEven 
def isOddViaReduction(inString):
    inStringPlusOne = int(inString) + 1
    return lastDigitIsEven(str(inStringPlusOne)) 

def testisOddViaReduction():
    testVals = [('-2', 'no'),
                ('0', 'no'),
                ('2', 'no'),
                ('3742788', 'no'),
                ('-1', 'yes'),
                ('1', 'yes'),
                ('3', 'yes'),
                ('17', 'yes'),
                ('3953969', 'yes'),
                ]
    for (inString, solution) in testVals:
        val = isOddViaReduction(inString)
        utils.tprint(inString, ':', val)
        assert val == solution
  • isOddViaReduction uses lastDigitIsEven to solve the isOdd problem
  • isOddViaReduction solves IsOdd by reduction it to LastDigitIsEven

What is a Turing reduction?

  • Formal definition:
    • Let \(F\) and \(G\) be computational problems
    • \(F\) has a Turing reduction to \(G\) if can solve \(F\) using a program for \(G\)
    • We write this as \(F \leq_T G\) (read this notation as “\(F\) reduces to \(G\)”)
  • Intuitive meanings:
    • \(F\) is easier than \(G\)” or “\(F\) is no harder than \(G\)
    • If \(G\) is solvable, then \(F\) is automatically solvable
    • If \(F\) is unsolvable, then \(G\) must also be unsolvable
    • Visually, write the notation \(F \leq_T G\) as \(F \rightarrow G\) in a diagram

Prove uncomputability using Turing reductions

  • Explore the properties of Turing reductions
  • By reducing from YesOnString:
    • Prove that ContainsGAGA is uncomputable
    • Prove that HaltsOnString is uncomputable
    • Importantly, many other reductions are also possible!
    • Done correctly, a reduction proves uncomputability
  • Visualize a tree of uncomputable problems and their relationships

YesOnString \(\rightarrow\) ContainsGAGA

  • Goal: Prove ContainsGAGA is uncomputable
  • Known fact: YesOnString is uncomputable
  • Strategy: Show YesOnString \(\leq_T\) ContainsGAGA
  • Problem definitions:
    • YesOnString: Given program \(P\) and input \(I\), does \(P(I)=\) “yes”?
    • ContainsGAGA: Given program \(P\) and input \(I\), does \(P(I)\) contain the string “GAGA”?
  • Reduction strategy:
    • Transform any instance of YesOnString into an instance of ContainsGAGA (i.e., ContainsGAGA will “call” YesOnString)
    • Use “oracle” for ContainsGAGA to solve YesOnString

Reduction as Python source code

import utils; from utils import rf
from GAGAOnString import GAGAOnString # oracle function  
def yesViaGAGA(progString, inString):
    singleString = utils.ESS(progString, inString)
    return GAGAOnString(rf('alterYesToGAGA.py'), singleString) 

def testYesViaGAGA():
    testvals = [
        ('containsGAGA.py', 'TTTTGAGATT', 'yes'),
        ('containsGAGA.py', 'TTTTGAGTT', 'no'),
        ('isEmpty.py', '', 'yes'),
        ('isEmpty.py', 'x', 'no'),
    ]
    for (filename, inString, solution) in testvals:
        val = yesViaGAGA(rf(filename), inString)
        utils.tprint(filename + ":", val)
        assert val == solution
  • Key idea: Solve the YesOnString problem using an “oracle” for ContainsGAGA, meaning we can solve known problem with new one

Why does this prove that ContainsGAGA is uncomputable?

  • Step 1: Assume ContainsGAGA is computable
  • Step 2: Then YesOnString is computable (via our reduction)
  • Step 3: But YesOnString is uncomputable, which we have previously proven in a separate proof by contradiction!
  • Contradiction: Cannot have both computable and uncomputable
  • Realization: Our initial assumption was wrong
  • Conclusion: ContainsGAGA must be uncomputable
  • This completes the proof that ContainsGAGA is uncomputable!

Tree of uncomputable problems

  • Starting point: YesOnString, previously proven uncomputable

  • First level reductions:

    • Reduce to: YesOnEmpty (does \(P(\epsilon)=\) “yes”?)
    • Reduce to: YesOnAll (does \(P(I)=\) “yes” for all \(I\)?)
    • Reduce to: YesOnSome (does \(P(I)=\) “yes” for some \(I\)?)
    • Reduce to: ContainsGAGA (does \(P(I)\) output contain “GAGA”?)
  • Second level reductions:

    • From halting: HaltsOnString, HaltsOnEmpty, HaltsOnAll
    • From counting: NumCharsOnString, NumStepsOnString
  • Next question: What are the properties of Turing reductions?

  • And, finally, how can we visualize a tree of reductions?

Properties of Turing reductions

  • Transitivity property:
    • Chain reductions: If \(F \leq_T G\) and \(G \leq_T H\), then \(F \leq_T H\)
    • Composition: Solve \(F\) using \(G\) oracle, solve \(G\) using \(H\) oracle
    • Combined: Can solve \(F\) using \(H\) oracle directly
  • Importantly, all of these uncomputability proofs start from a single problem, such as YesOnString, that we’ve proven to be uncomputable!

  • Building hierarchies: Transitivity helps organize problems by hardness

  • Viewing hierarchies: See the relationships between problems in a tree

  • Tree Interpretation: Node at the top is the “starting point” of the Turing reduction chain! Okay, let’s see what this looks like!

Tree of uncomputable problems

What are different ways that you can express the tree’s relationships?

  • YesOnString is at least as hard as HaltsOnString
  • HaltsOnString is at least as hard as HaltsOnEmpty
  • HaltsOnString is at least as hard as NumStepsOnString
  • YesOnString is at least as hard as HaltsOnEmpty
  • YesOnString is at least as hard as NumStepsOnString

YesOnString to YesOnEmpty

import utils; from utils import rf
from yesOnEmpty import yesOnEmpty # oracle function  
def yesViaEmpty(progString, inString):
    utils.writeFile('progString.txt', progString)
    utils.writeFile('inString.txt', inString)
    return yesOnEmpty(rf('ignoreInput.py')) 
  • Use the “ignore input” technique to transform the problem
  • Ignoring input helps to create the correct reduction:
    • YesOnString \(\leq_T\) YesOnEmpty
    • YesOnEmpty is at least as hard as YesOnString
    • This approach shows that YesOnEmpty is uncomputable
  • Can we prove that haltOnString is uncomputable?

alterYesToHalt program

import utils; from utils import rf
from universal import universal 
def alterYesToHalt(inString):
    (progString, newInString) = utils.DESS(inString)
    val = universal(progString, newInString)
    if val == 'yes':
        # return value is irrelevant, since returning any string halts
        return 'halted' 
    else:
        # deliberately enter infinite loop
        utils.loop()  

def testAlterYesToHalt():
    for (progName, inString, solution) in [
            ('containsGAGA.py', 'GAGAGAGAG', 'halted'),
            ('containsGAGA.py', 'TTTTGGCCGGT', None),
    ]:
        combinedString = utils.ESS(rf(progName), inString)
        val = utils.runWithTimeout(None, alterYesToHalt, combinedString)
        utils.tprint( (progName, inString), ":", val )
        assert val == solution
  • The alterYesToHalt will be used in the next reduction!
  • Remember, alterYesToHalt uses source code provided by the textbook

Create yesViaHalt program

import utils; from utils import rf
from haltsOnString import haltsOnString # oracle function  
def yesViaHalts(progString, inString):
    singleStr = utils.ESS(progString, inString)
    return haltsOnString(rf('alterYesToHalt.py'), singleStr) 

def testYesViaHalts():
    testvals = [
        ('containsGAGA.py', 'TTTTGAGATT', 'yes'),
        ('containsGAGA.py', 'TTTTGAGTT', 'no'),
        ('isEmpty.py', '', 'yes'),
        ('isEmpty.py', 'x', 'no'),
    ]
    for (filename, inString, solution) in testvals:
        val = yesViaHalts(rf(filename), inString)
        utils.tprint(filename + ":", val)
        assert val == solution
  • Use the haltsOnString “oracle” to solve YesOnString, meaning YesOnString \(\leq_T\) HaltsOnString. Wow, we have shown that HaltsOnString is uncomputable!

Reduction proof strategy

Key takeaways for proofgrammers

  • Key concepts learned this week:
    • Turing reductions: Way to prove problems uncomputable
    • Oracle methodology: Assume solution exists to build contradiction
    • Proof by contradiction: Powerful technique for impossibility results
    • Problem hierarchies: Organize computational problems by difficulty
    • Realization: From YesOnString, many problems are uncomputable!
  • Practical skills developed this week:
    • Program transformation: Modify programs to change their behavior
    • Reduction construction: Build systematic arguments for hardness
    • Mathematical reasoning: Formal logic for computational problems