diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index 614d6c18ee0b4a..1ca1a576fb2329 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -227,6 +227,7 @@ struct _typeobject {
 
     destructor tp_finalize;
     vectorcallfunc tp_vectorcall;
+    size_t tp_static_builtin_index;  /* 0 means "not initialized" */
 };
 
 /* This struct is used by the specializer
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index 6ce2945cd9fb7b..d71386953a0dd0 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -173,7 +173,7 @@ struct _is {
     struct _Py_exc_state exc_state;
 
     struct ast_state ast;
-    struct type_cache type_cache;
+    struct types_state types;
     struct callable_cache callable_cache;
 
     /* The following fields are here to avoid allocation during init.
diff --git a/Include/internal/pycore_structseq.h b/Include/internal/pycore_structseq.h
index 0199c790e24cec..d10a921c55ff8b 100644
--- a/Include/internal/pycore_structseq.h
+++ b/Include/internal/pycore_structseq.h
@@ -15,11 +15,18 @@ PyAPI_FUNC(PyTypeObject *) _PyStructSequence_NewType(
     PyStructSequence_Desc *desc,
     unsigned long tp_flags);
 
-PyAPI_FUNC(int) _PyStructSequence_InitType(
+PyAPI_FUNC(int) _PyStructSequence_InitBuiltinWithFlags(
     PyTypeObject *type,
     PyStructSequence_Desc *desc,
     unsigned long tp_flags);
 
+static inline int
+_PyStructSequence_InitBuiltin(PyTypeObject *type,
+                              PyStructSequence_Desc *desc)
+{
+    return _PyStructSequence_InitBuiltinWithFlags(type, desc, 0);
+}
+
 extern void _PyStructSequence_FiniType(PyTypeObject *type);
 
 #ifdef __cplusplus
diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h
index c480a3a57b436c..545e28ac32f5fc 100644
--- a/Include/internal/pycore_typeobject.h
+++ b/Include/internal/pycore_typeobject.h
@@ -39,8 +39,25 @@ struct type_cache {
 #endif
 };
 
+/* For now we hard-code this to a value for which we are confident
+   all the static builtin types will fit (for all builds). */
+#define _Py_MAX_STATIC_BUILTIN_TYPES 200
+
+typedef struct {
+    PyTypeObject *type;
+} static_builtin_type_state;
+
+struct types_state {
+    struct type_cache type_cache;
+    size_t num_builtins_initialized;
+    static_builtin_type_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES];
+};
+
+
 extern PyStatus _PyTypes_InitSlotDefs(void);
 
+extern int _PyStaticType_InitBuiltin(PyTypeObject *type);
+extern static_builtin_type_state * _PyStaticType_GetState(PyTypeObject *);
 extern void _PyStaticType_Dealloc(PyTypeObject *type);
 
 
diff --git a/Include/object.h b/Include/object.h
index a12b754c8ed8f3..c00b9eb583420a 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -352,6 +352,9 @@ given type object has a specified feature.
 
 #ifndef Py_LIMITED_API
 
+/* Track types initialized using _PyStaticType_InitBuiltin(). */
+#define _Py_TPFLAGS_STATIC_BUILTIN (1 << 1)
+
 /* Placement of dict (and values) pointers are managed by the VM, not by the type.
  * The VM will automatically set tp_dictoffset. Should not be used for variable sized
  * classes, such as classes that extend tuple.
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 1dc10d8b0a39ac..f629dd5f034728 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1507,7 +1507,7 @@ def delx(self): del self.__x
         check((1,2,3), vsize('') + 3*self.P)
         # type
         # static type: PyTypeObject
-        fmt = 'P2nPI13Pl4Pn9Pn12PIP'
+        fmt = 'P2nPI13Pl4Pn9Pn12PIPI'
         s = vsize('2P' + fmt)
         check(int, s)
         # class
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
index 9aab683fd1ca7f..e06a8f4173cf8b 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -3556,8 +3556,7 @@ _PyExc_InitTypes(PyInterpreterState *interp)
 
     for (size_t i=0; i < Py_ARRAY_LENGTH(static_exceptions); i++) {
         PyTypeObject *exc = static_exceptions[i].exc;
-
-        if (PyType_Ready(exc) < 0) {
+        if (_PyStaticType_InitBuiltin(exc) < 0) {
             return -1;
         }
     }
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index 47d308b8eb3702..4b1b24f2e702a4 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -1992,7 +1992,8 @@ _PyFloat_InitTypes(PyInterpreterState *interp)
 
     /* Init float info */
     if (FloatInfoType.tp_name == NULL) {
-        if (PyStructSequence_InitType2(&FloatInfoType, &floatinfo_desc) < 0) {
+        if (_PyStructSequence_InitBuiltin(&FloatInfoType,
+                                          &floatinfo_desc) < 0) {
             return _PyStatus_ERR("can't init float info type");
         }
     }
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 4a1e5caf5a2843..90ed02b8c27a19 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -6135,7 +6135,7 @@ _PyLong_InitTypes(PyInterpreterState *interp)
 
     /* initialize int_info */
     if (Int_InfoType.tp_name == NULL) {
-        if (PyStructSequence_InitType2(&Int_InfoType, &int_info_desc) < 0) {
+        if (_PyStructSequence_InitBuiltin(&Int_InfoType, &int_info_desc) < 0) {
             return _PyStatus_ERR("can't init int info type");
         }
     }
diff --git a/Objects/object.c b/Objects/object.c
index 75aee909a3b496..758b79eff5b3e3 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -1975,8 +1975,8 @@ _PyTypes_InitTypes(PyInterpreterState *interp)
     // All other static types (unless initialized elsewhere)
     for (size_t i=0; i < Py_ARRAY_LENGTH(static_types); i++) {
         PyTypeObject *type = static_types[i];
-        if (PyType_Ready(type) < 0) {
-            return _PyStatus_ERR("Can't initialize types");
+        if (_PyStaticType_InitBuiltin(type) < 0) {
+            return _PyStatus_ERR("Can't initialize builtin type");
         }
         if (type == &PyType_Type) {
             // Sanitify checks of the two most important types
diff --git a/Objects/structseq.c b/Objects/structseq.c
index 229e3d893ff6aa..3ac2276acd725b 100644
--- a/Objects/structseq.c
+++ b/Objects/structseq.c
@@ -432,11 +432,21 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict,
     return -1;
 }
 
-static void
-initialize_members(PyStructSequence_Desc *desc, PyMemberDef* members,
-                   Py_ssize_t n_members) {
-    Py_ssize_t i, k;
+static PyMemberDef *
+initialize_members(PyStructSequence_Desc *desc,
+                   Py_ssize_t *pn_members, Py_ssize_t *pn_unnamed_members)
+{
+    PyMemberDef *members;
+    Py_ssize_t n_members, n_unnamed_members;
+
+    n_members = count_members(desc, &n_unnamed_members);
+    members = PyMem_NEW(PyMemberDef, n_members - n_unnamed_members + 1);
+    if (members == NULL) {
+        PyErr_NoMemory();
+        return NULL;
+    }
 
+    Py_ssize_t i, k;
     for (i = k = 0; i < n_members; ++i) {
         if (desc->fields[i].name == PyStructSequence_UnnamedField) {
             continue;
@@ -453,30 +463,17 @@ initialize_members(PyStructSequence_Desc *desc, PyMemberDef* members,
         k++;
     }
     members[k].name = NULL;
+
+    *pn_members = n_members;
+    *pn_unnamed_members = n_unnamed_members;
+    return members;
 }
 
 
-int
-_PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc,
-                           unsigned long tp_flags)
+static void
+initialize_static_fields(PyTypeObject *type, PyStructSequence_Desc *desc,
+                         PyMemberDef *tp_members, unsigned long tp_flags)
 {
-    PyMemberDef *members;
-    Py_ssize_t n_members, n_unnamed_members;
-
-#ifdef Py_TRACE_REFS
-    /* if the type object was chained, unchain it first
-       before overwriting its storage */
-    if (type->ob_base.ob_base._ob_next) {
-        _Py_ForgetReference((PyObject *)type);
-    }
-#endif
-
-    /* PyTypeObject has already been initialized */
-    if (Py_REFCNT(type) != 0) {
-        PyErr_BadInternalCall();
-        return -1;
-    }
-
     type->tp_name = desc->name;
     type->tp_basicsize = sizeof(PyStructSequence) - sizeof(PyObject *);
     type->tp_itemsize = sizeof(PyObject *);
@@ -488,25 +485,20 @@ _PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc,
     type->tp_new = structseq_new;
     type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | tp_flags;
     type->tp_traverse = (traverseproc) structseq_traverse;
+    type->tp_members = tp_members;
+}
 
-    n_members = count_members(desc, &n_unnamed_members);
-    members = PyMem_NEW(PyMemberDef, n_members - n_unnamed_members + 1);
-    if (members == NULL) {
-        PyErr_NoMemory();
-        return -1;
-    }
-    initialize_members(desc, members, n_members);
-    type->tp_members = members;
-
+static int
+initialize_static_type(PyTypeObject *type, PyStructSequence_Desc *desc,
+                       Py_ssize_t n_members, Py_ssize_t n_unnamed_members) {
+    /* initialize_static_fields() should have been called already. */
     if (PyType_Ready(type) < 0) {
-        PyMem_Free(members);
         return -1;
     }
     Py_INCREF(type);
 
     if (initialize_structseq_dict(
             desc, type->tp_dict, n_members, n_unnamed_members) < 0) {
-        PyMem_Free(members);
         Py_DECREF(type);
         return -1;
     }
@@ -514,10 +506,62 @@ _PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc,
     return 0;
 }
 
+int
+_PyStructSequence_InitBuiltinWithFlags(PyTypeObject *type,
+                                       PyStructSequence_Desc *desc,
+                                       unsigned long tp_flags)
+{
+    PyMemberDef *members;
+    Py_ssize_t n_members, n_unnamed_members;
+
+    members = initialize_members(desc, &n_members, &n_unnamed_members);
+    if (members == NULL) {
+        return -1;
+    }
+    initialize_static_fields(type, desc, members, tp_flags);
+    if (_PyStaticType_InitBuiltin(type) < 0) {
+        PyErr_Format(PyExc_RuntimeError,
+                     "Can't initialize builtin type %s",
+                     desc->name);
+        return -1;
+    }
+    if (initialize_static_type(type, desc, n_members, n_unnamed_members) < 0) {
+        PyMem_Free(members);
+        return -1;
+    }
+    return 0;
+}
+
 int
 PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc)
 {
-    return _PyStructSequence_InitType(type, desc, 0);
+    PyMemberDef *members;
+    Py_ssize_t n_members, n_unnamed_members;
+
+#ifdef Py_TRACE_REFS
+    /* if the type object was chained, unchain it first
+       before overwriting its storage */
+    if (type->ob_base.ob_base._ob_next) {
+        _Py_ForgetReference((PyObject *)type);
+    }
+#endif
+
+    /* PyTypeObject has already been initialized */
+    if (Py_REFCNT(type) != 0) {
+        PyErr_BadInternalCall();
+        return -1;
+    }
+
+    members = initialize_members(desc, &n_members, &n_unnamed_members);
+    if (members == NULL) {
+        return -1;
+    }
+    initialize_static_fields(type, desc, members, 0);
+    if (initialize_static_type(type, desc, n_members, n_unnamed_members) < 0) {
+        PyMem_Free(members);
+        return -1;
+    }
+    return 0;
 }
 
 void
@@ -569,13 +613,10 @@ _PyStructSequence_NewType(PyStructSequence_Desc *desc, unsigned long tp_flags)
     Py_ssize_t n_members, n_unnamed_members;
 
     /* Initialize MemberDefs */
-    n_members = count_members(desc, &n_unnamed_members);
-    members = PyMem_NEW(PyMemberDef, n_members - n_unnamed_members + 1);
+    members = initialize_members(desc, &n_members, &n_unnamed_members);
     if (members == NULL) {
-        PyErr_NoMemory();
         return NULL;
     }
-    initialize_members(desc, members, n_members);
 
     /* Initialize Slots */
     slots[0] = (PyType_Slot){Py_tp_dealloc, (destructor)structseq_dealloc};
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 5ebff6084f4a97..c301daca0e86a1 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -65,6 +65,8 @@ lookup_maybe_method(PyObject *self, PyObject *attr, int *unbound);
 static int
 slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value);
 
+static inline PyTypeObject * subclass_from_ref(PyObject *ref);
+
 /*
  * finds the beginning of the docstring's introspection signature.
  * if present, returns a pointer pointing to the first '('.
@@ -204,7 +206,7 @@ static struct type_cache*
 get_type_cache(void)
 {
     PyInterpreterState *interp = _PyInterpreterState_GET();
-    return &interp->type_cache;
+    return &interp->types.type_cache;
 }
 
 
@@ -223,7 +225,7 @@ type_cache_clear(struct type_cache *cache, PyObject *value)
 void
 _PyType_InitCache(PyInterpreterState *interp)
 {
-    struct type_cache *cache = &interp->type_cache;
+    struct type_cache *cache = &interp->types.type_cache;
     for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) {
         struct type_cache_entry *entry = &cache->hashtable[i];
         assert(entry->name == NULL);
@@ -240,7 +242,7 @@ _PyType_InitCache(PyInterpreterState *interp)
 static unsigned int
 _PyType_ClearCache(PyInterpreterState *interp)
 {
-    struct type_cache *cache = &interp->type_cache;
+    struct type_cache *cache = &interp->types.type_cache;
 #if MCACHE_STATS
     size_t total = cache->hits + cache->collisions + cache->misses;
     fprintf(stderr, "-- Method cache hits        = %zd (%d%%)\n",
@@ -272,11 +274,17 @@ PyType_ClearCache(void)
 void
 _PyTypes_Fini(PyInterpreterState *interp)
 {
-    struct type_cache *cache = &interp->type_cache;
+    struct type_cache *cache = &interp->types.type_cache;
     type_cache_clear(cache, NULL);
     if (_Py_IsMainInterpreter(interp)) {
         clear_slotdefs();
     }
+
+    assert(interp->types.num_builtins_initialized == 0);
+    // All the static builtin types should have been finalized already.
+    for (size_t i = 0; i < _Py_MAX_STATIC_BUILTIN_TYPES; i++) {
+        assert(interp->types.builtins[i].type == NULL);
+    }
 }
 
 
@@ -309,12 +317,11 @@ PyType_Modified(PyTypeObject *type)
         Py_ssize_t i = 0;
         PyObject *ref;
         while (PyDict_Next(subclasses, &i, NULL, &ref)) {
-            assert(PyWeakref_CheckRef(ref));
-            PyObject *obj = PyWeakref_GET_OBJECT(ref);
-            if (obj == Py_None) {
+            PyTypeObject *subclass = subclass_from_ref(ref);  // borrowed
+            if (subclass == NULL) {
                 continue;
             }
-            PyType_Modified(_PyType_CAST(obj));
+            PyType_Modified(subclass);
         }
     }
 
@@ -4211,23 +4218,63 @@ type_dealloc_common(PyTypeObject *type)
 }
 
 
-void
-_PyStaticType_Dealloc(PyTypeObject *type)
+static void
+clear_static_tp_subclasses(PyTypeObject *type)
 {
-    // If a type still has subtypes, it cannot be deallocated.
-    // A subtype can inherit attributes and methods of its parent type,
-    // and a type must no longer be used once it's deallocated.
-    if (type->tp_subclasses != NULL) {
+    if (type->tp_subclasses == NULL) {
         return;
     }
 
+    /* Normally it would be a problem to finalize the type if its
+       tp_subclasses wasn't cleared first.  However, this is only
+       ever called at the end of runtime finalization, so we can be
+       more liberal in cleaning up.  If the given type still has
+       subtypes at this point then some extension module did not
+       correctly finalize its objects.
+
+       We can safely obliterate such sybtypes since the extension
+       module and its objects won't be used again, except maybe if
+       the runtime were re-initialized.  In that case the sticky
+       situation would only happen if the module were re-imported
+       then and only if the subtype were stored in a global and only
+       if that global were not overwritten during import.  We'd be
+       fine since the extension is otherwise unsafe and unsupported
+       in that situation, and likely problematic already.
+
+       In any case, this situation means at least some memory is
+       going to leak.  This mostly only affects embedding scenarios.
+     */
+
+    // For now we just do a sanity check and then drop tp_subclasses.
+    Py_ssize_t i = 0;
+    PyObject *ref;  // borrowed ref
+    while (PyDict_Next((PyObject *)type->tp_subclasses, &i, NULL, &ref)) {
+        PyTypeObject *subclass = subclass_from_ref(ref);  // borrowed
+        if (subclass == NULL) {
+            continue;
+        }
+        // All static builtin subtypes should have been finalized already.
+        assert(!(subclass->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
+    }
+
+    Py_CLEAR(type->tp_subclasses);
+}
+
+static inline size_t get_static_builtin_index(PyTypeObject *);
+static inline void set_static_builtin_index(PyTypeObject *, size_t);
+
+void
+_PyStaticType_Dealloc(PyTypeObject *type)
+{
+    assert(!(type->tp_flags & Py_TPFLAGS_HEAPTYPE));
+
     type_dealloc_common(type);
 
     Py_CLEAR(type->tp_dict);
     Py_CLEAR(type->tp_bases);
     Py_CLEAR(type->tp_mro);
     Py_CLEAR(type->tp_cache);
-    // type->tp_subclasses is NULL
+    clear_static_tp_subclasses(type);
 
     // PyObject_ClearWeakRefs() raises an exception if Py_REFCNT() != 0
     if (Py_REFCNT(type) == 0) {
@@ -4235,6 +4282,20 @@ _PyStaticType_Dealloc(PyTypeObject *type)
     }
 
     type->tp_flags &= ~Py_TPFLAGS_READY;
+
+    if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
+        /* Reset the type's per-interpreter state.
+           This basically undoes what _PyStaticType_InitBuiltin() did. */
+        assert(get_static_builtin_index(type) > 0);
+        static_builtin_type_state *state = _PyStaticType_GetState(type);
+        assert(state != NULL);
+        state->type = NULL;
+        set_static_builtin_index(type, 0);
+
+        PyInterpreterState *interp = _PyInterpreterState_GET();
+        assert(interp->types.num_builtins_initialized > 0);
+        interp->types.num_builtins_initialized--;
+    }
 }
 
 
@@ -4296,14 +4357,12 @@ _PyType_GetSubclasses(PyTypeObject *self)
     Py_ssize_t i = 0;
     PyObject *ref;  // borrowed ref
     while (PyDict_Next(subclasses, &i, NULL, &ref)) {
-        assert(PyWeakref_CheckRef(ref));
-        PyObject *obj = PyWeakref_GET_OBJECT(ref);  // borrowed ref
-        if (obj == Py_None) {
+        PyTypeObject *subclass = subclass_from_ref(ref);  // borrowed
+        if (subclass == NULL) {
             continue;
         }
-        assert(PyType_Check(obj));
 
-        if (PyList_Append(list, obj) < 0) {
+        if (PyList_Append(list, _PyObject_CAST(subclass)) < 0) {
             Py_DECREF(list);
             return NULL;
         }
@@ -6651,6 +6710,53 @@ PyType_Ready(PyTypeObject *type)
 }
 
 
+static inline size_t
+get_static_builtin_index(PyTypeObject *self)
+{
+    return self->tp_static_builtin_index;
+}
+
+static inline void
+set_static_builtin_index(PyTypeObject *self, size_t index)
+{
+    self->tp_static_builtin_index = index;
+}
+
+int
+_PyStaticType_InitBuiltin(PyTypeObject *self)
+{
+    /* It should only be called once for each builtin type. */
+    assert(get_static_builtin_index(self) == 0);
+
+    /* For static types we store some state in an array on each interpreter. */
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    interp->types.num_builtins_initialized++;
+    assert(interp->types.num_builtins_initialized < _Py_MAX_STATIC_BUILTIN_TYPES);
+
+    /* We use 1-based indexing so 0 can mean "not initialized". */
+    set_static_builtin_index(self, interp->types.num_builtins_initialized);
+
+    /* Now we initialize the type's per-interpreter state. */
+    static_builtin_type_state *state = _PyStaticType_GetState(self);
+    assert(state != NULL);
+    state->type = self;
+
+    self->tp_flags = self->tp_flags | _Py_TPFLAGS_STATIC_BUILTIN;
+
+    return PyType_Ready(self);
+}
+
+static_builtin_type_state *
+_PyStaticType_GetState(PyTypeObject *self)
+{
+    if (get_static_builtin_index(self) == 0) {
+        return NULL;
+    }
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    return &(interp->types.builtins[get_static_builtin_index(self) - 1]);
+}
+
+
 static int
 add_subclass(PyTypeObject *base, PyTypeObject *type)
 {
@@ -6700,6 +6806,42 @@ add_all_subclasses(PyTypeObject *type, PyObject *bases)
     return res;
 }
 
+static inline PyTypeObject *
+subclass_from_ref(PyObject *ref)
+{
+    assert(PyWeakref_CheckRef(ref));
+    PyObject *obj = PyWeakref_GET_OBJECT(ref);  // borrowed ref
+    assert(obj != NULL);
+    if (obj == Py_None) {
+        return NULL;
+    }
+    assert(PyType_Check(obj));
+    return _PyType_CAST(obj);
+}
+
+static PyObject *
+get_subclasses_key(PyTypeObject *type, PyTypeObject *base)
+{
+    PyObject *key = PyLong_FromVoidPtr((void *) type);
+    if (key != NULL) {
+        return key;
+    }
+    PyErr_Clear();
+
+    /* This basically means we're out of memory.
+       We fall back to manually traversing the values. */
+    Py_ssize_t i = 0;
+    PyObject *ref;  // borrowed ref
+    while (PyDict_Next((PyObject *)base->tp_subclasses, &i, &key, &ref)) {
+        PyTypeObject *subclass = subclass_from_ref(ref);  // borrowed
+        if (subclass == type) {
+            return Py_NewRef(key);
+        }
+    }
+    /* It wasn't found. */
+    return NULL;
+}
+
 static void
 remove_subclass(PyTypeObject *base, PyTypeObject *type)
 {
@@ -6709,8 +6851,8 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
     }
     assert(PyDict_CheckExact(subclasses));
 
-    PyObject *key = PyLong_FromVoidPtr((void *) type);
-    if (key == NULL || PyDict_DelItem(subclasses, key)) {
+    PyObject *key = get_subclasses_key(type, base);
+    if (key != NULL && PyDict_DelItem(subclasses, key)) {
         /* This can happen if the type initialization errored out before
            the base subclasses were updated (e.g. a non-str __qualname__
            was passed in the type dict). */
@@ -8803,13 +8945,10 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name,
     Py_ssize_t i = 0;
     PyObject *ref;
     while (PyDict_Next(subclasses, &i, NULL, &ref)) {
-        assert(PyWeakref_CheckRef(ref));
-        PyObject *obj = PyWeakref_GET_OBJECT(ref);
-        assert(obj != NULL);
-        if (obj == Py_None) {
+        PyTypeObject *subclass = subclass_from_ref(ref);  // borrowed
+        if (subclass == NULL) {
             continue;
         }
-        PyTypeObject *subclass = _PyType_CAST(obj);
 
         /* Avoid recursing down into unaffected classes */
         PyObject *dict = subclass->tp_dict;
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index 669ffe70727468..7e3caf1af14c5b 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -14604,13 +14604,13 @@ _PyUnicode_InitTypes(PyInterpreterState *interp)
         return _PyStatus_OK();
     }
 
-    if (PyType_Ready(&EncodingMapType) < 0) {
+    if (_PyStaticType_InitBuiltin(&EncodingMapType) < 0) {
         goto error;
     }
-    if (PyType_Ready(&PyFieldNameIter_Type) < 0) {
+    if (_PyStaticType_InitBuiltin(&PyFieldNameIter_Type) < 0) {
         goto error;
     }
-    if (PyType_Ready(&PyFormatterIter_Type) < 0) {
+    if (_PyStaticType_InitBuiltin(&PyFormatterIter_Type) < 0) {
         goto error;
     }
     return _PyStatus_OK();
diff --git a/Python/errors.c b/Python/errors.c
index b6b5d9b046ce85..2aa748c60c3704 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -1229,8 +1229,8 @@ _PyErr_InitTypes(PyInterpreterState *interp)
     }
 
     if (UnraisableHookArgsType.tp_name == NULL) {
-        if (PyStructSequence_InitType2(&UnraisableHookArgsType,
-                                       &UnraisableHookArgs_desc) < 0) {
+        if (_PyStructSequence_InitBuiltin(&UnraisableHookArgsType,
+                                          &UnraisableHookArgs_desc) < 0) {
             return _PyStatus_ERR("failed to initialize UnraisableHookArgs type");
         }
     }
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 65e7f23e963b5f..75aba4a25c41a9 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -1672,9 +1672,10 @@ finalize_interp_types(PyInterpreterState *interp)
     _PyLong_FiniTypes(interp);
     _PyThread_FiniType(interp);
     _PyErr_FiniTypes(interp);
-    _PyTypes_Fini(interp);
     _PyTypes_FiniTypes(interp);
 
+    _PyTypes_Fini(interp);
+
     // Call _PyUnicode_ClearInterned() before _PyDict_Fini() since it uses
     // a dict internally.
     _PyUnicode_ClearInterned(interp);
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index a5fa551b95764c..e861d9cbce415c 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -28,7 +28,7 @@ Data members:
 #include "pycore_pymath.h"        // _PY_SHORT_FLOAT_REPR
 #include "pycore_pymem.h"         // _PyMem_SetDefaultAllocator()
 #include "pycore_pystate.h"       // _PyThreadState_GET()
-#include "pycore_structseq.h"     // _PyStructSequence_InitType()
+#include "pycore_structseq.h"     // _PyStructSequence_InitBuiltinWithFlags()
 #include "pycore_tuple.h"         // _PyTuple_FromArray()
 
 #include "frameobject.h"          // PyFrame_FastToLocalsWithError()
@@ -2921,7 +2921,7 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict)
     SET_SYS("int_info", PyLong_GetInfo());
     /* initialize hash_info */
     if (Hash_InfoType.tp_name == NULL) {
-        if (PyStructSequence_InitType2(&Hash_InfoType, &hash_info_desc) < 0) {
+        if (_PyStructSequence_InitBuiltin(&Hash_InfoType, &hash_info_desc) < 0) {
             goto type_init_failed;
         }
     }
@@ -2943,14 +2943,18 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict)
     SET_SYS_FROM_STRING("abiflags", ABIFLAGS);
 #endif
 
+#define ENSURE_INFO_TYPE(TYPE, DESC) \
+    do { \
+        if (TYPE.tp_name == NULL) { \
+            if (_PyStructSequence_InitBuiltinWithFlags( \
+                    &TYPE, &DESC, Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) { \
+                goto type_init_failed; \
+            } \
+        } \
+    } while (0)
+
     /* version_info */
-    if (VersionInfoType.tp_name == NULL) {
-        if (_PyStructSequence_InitType(&VersionInfoType,
-                                       &version_info_desc,
-                                       Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) {
-            goto type_init_failed;
-        }
-    }
+    ENSURE_INFO_TYPE(VersionInfoType, version_info_desc);
     version_info = make_version_info(tstate);
     SET_SYS("version_info", version_info);
 
@@ -2958,27 +2962,18 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict)
     SET_SYS("implementation", make_impl_info(version_info));
 
     // sys.flags: updated in-place later by _PySys_UpdateConfig()
-    if (FlagsType.tp_name == 0) {
-        if (_PyStructSequence_InitType(&FlagsType, &flags_desc,
-                                       Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) {
-            goto type_init_failed;
-        }
-    }
+    ENSURE_INFO_TYPE(FlagsType, flags_desc);
     SET_SYS("flags", make_flags(tstate->interp));
 
 #if defined(MS_WINDOWS)
     /* getwindowsversion */
-    if (WindowsVersionType.tp_name == 0) {
-        if (_PyStructSequence_InitType(&WindowsVersionType,
-                                       &windows_version_desc,
-                                       Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) {
-            goto type_init_failed;
-        }
-    }
+    ENSURE_INFO_TYPE(WindowsVersionType, windows_version_desc);
 
     SET_SYS_FROM_STRING("_vpath", VPATH);
 #endif
 
+#undef ENSURE_INFO_TYPE
+
     /* float repr style: 0.03 (short) vs 0.029999999999999999 (legacy) */
 #if _PY_SHORT_FLOAT_REPR == 1
     SET_SYS_FROM_STRING("float_repr_style", "short");
@@ -2990,7 +2985,7 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict)
 
     /* initialize asyncgen_hooks */
     if (AsyncGenHooksType.tp_name == NULL) {
-        if (PyStructSequence_InitType2(
+        if (_PyStructSequence_InitBuiltin(
                 &AsyncGenHooksType, &asyncgen_hooks_desc) < 0) {
             goto type_init_failed;
         }
diff --git a/Python/thread.c b/Python/thread.c
index 846f02545271cf..d3211219dffe6a 100644
--- a/Python/thread.c
+++ b/Python/thread.c
@@ -155,7 +155,8 @@ PyThread_GetInfo(void)
 #endif
 
     if (ThreadInfoType.tp_name == 0) {
-        if (PyStructSequence_InitType2(&ThreadInfoType, &threadinfo_desc) < 0)
+        if (_PyStructSequence_InitBuiltin(&ThreadInfoType,
+                                          &threadinfo_desc) < 0)
             return NULL;
     }