diff --git src/wp-includes/class-wp-term-query.php src/wp-includes/class-wp-term-query.php
index 4b949c6e16..b2224ed0da 100644
--- src/wp-includes/class-wp-term-query.php
+++ src/wp-includes/class-wp-term-query.php
@@ -672,6 +672,26 @@ class WP_Term_Query {
 
 		$this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
 
+		$this->terms = null;
+
+		/**
+		 * Filter the terms data before the query takes place.
+		 *
+		 * Return a non-null value to bypass WordPress's default term queries.
+		 *
+		 * @since 5.3.0
+		 *
+		 * @param array|null    $terms Return an array of term data to short-circuit WP's term query,
+		 *                             or null to allow WP queries to run normally.
+		 * @param WP_Term_Query $this  The WP_Term_Query instance, passed by reference.
+		 *
+		 */
+		$this->terms = apply_filters_ref_array( 'terms_pre_query', array( $this->terms, &$this ) );
+
+		if ( null !== $this->terms ) {
+			return $this->terms;
+		}
+
 		// $args can be anything. Only use the args defined in defaults to compute the key.
 		$key          = md5( serialize( wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ) ) . serialize( $taxonomies ) . $this->request );
 		$last_changed = wp_cache_get_last_changed( 'terms' );
diff --git tests/phpunit/tests/term/query.php tests/phpunit/tests/term/query.php
index f40a9b55e7..4daac1c35f 100644
--- tests/phpunit/tests/term/query.php
+++ tests/phpunit/tests/term/query.php
@@ -737,4 +737,34 @@ class Tests_Term_Query extends WP_UnitTestCase {
 
 		return $term;
 	}
+
+	/**
+	 * @ticket 41246
+	 */
+	public function test_terms_pre_query_filter_should_bypass_database_query() {
+		global $wpdb;
+
+		add_filter( 'terms_pre_query', array( __CLASS__, 'filter_terms_pre_query' ), 10, 2 );
+
+		$num_queries = $wpdb->num_queries;
+
+		$q       = new WP_Term_Query();
+		$results = $q->query(
+			array(
+				'fields' => 'ids',
+			)
+		);
+
+		remove_filter( 'terms_pre_query', array( __CLASS__, 'filter_terms_pre_query' ), 10, 2 );
+
+		// Make sure no queries were executed.
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+
+		// We manually inserted a non-existing term and overrode the results with it.
+		$this->assertSame( array( 555 ), $q->terms );
+	}
+
+	public static function filter_terms_pre_query( $terms, $query ) {
+		return array( 555 );
+	}
 }
