diff --git a/py/compile.c b/py/compile.c
index b3a83715e059d0df1b350f9a0ecd72d125fb4b63..48c9c08826d17e584d70f4fc3502c6be056efc5b 100644
--- a/py/compile.c
+++ b/py/compile.c
@@ -3312,7 +3312,8 @@ mp_obj_t mp_compile(mp_parse_node_t pn, qstr source_file, bool is_repl) {
         return mp_const_true;
 #else
         // return function that executes the outer module
-        return rt_make_function_from_id(unique_code_id, MP_OBJ_NULL);
+        // we can free the unique_code slot because no-one has reference to this unique_code_id anymore
+        return rt_make_function_from_id(unique_code_id, true, MP_OBJ_NULL);
 #endif
     }
 }
diff --git a/py/emitglue.c b/py/emitglue.c
index ed816a51619f300e6d60fddc3137be1b1aba7764..47b1791eb7b59ca2dcb83695ab512795fe1b373f 100644
--- a/py/emitglue.c
+++ b/py/emitglue.c
@@ -1,6 +1,7 @@
 // This code glues the code emitters to the runtime.
 
 #include <stdio.h>
+#include <string.h>
 #include <assert.h>
 
 #include "misc.h"
@@ -23,7 +24,8 @@
 #endif
 
 typedef enum {
-    MP_CODE_NONE,
+    MP_CODE_UNUSED,
+    MP_CODE_RESERVED,
     MP_CODE_BYTE,
     MP_CODE_NATIVE,
     MP_CODE_INLINE_ASM,
@@ -49,16 +51,16 @@ typedef struct _mp_code_t {
 } mp_code_t;
 
 STATIC machine_uint_t unique_codes_alloc = 0;
+STATIC machine_uint_t unique_codes_total = 0; // always >= unique_codes_alloc
 STATIC mp_code_t *unique_codes = NULL;
-STATIC uint next_unique_code_id;
 
 #ifdef WRITE_CODE
 FILE *fp_write_code = NULL;
 #endif
 
 void mp_emit_glue_init(void) {
-    next_unique_code_id = 0;
     unique_codes_alloc = 0;
+    unique_codes_total = 0;
     unique_codes = NULL;
 
 #ifdef WRITE_CODE
@@ -77,25 +79,34 @@ void mp_emit_glue_deinit(void) {
 }
 
 uint mp_emit_glue_get_unique_code_id(void) {
-    return next_unique_code_id++;
+    // look for an existing unused slot
+    for (uint i = 0; i < unique_codes_alloc; i++) {
+        if (unique_codes[i].kind == MP_CODE_UNUSED) {
+            unique_codes[i].kind = MP_CODE_RESERVED;
+            return i;
+        }
+    }
+    // no existing slot
+    // return next available id, memory will be allocated later
+    return unique_codes_total++;
 }
 
 STATIC void mp_emit_glue_alloc_unique_codes(void) {
-    if (next_unique_code_id > unique_codes_alloc) {
-        DEBUG_printf("allocate more unique codes: " UINT_FMT " -> %u\n", unique_codes_alloc, next_unique_code_id);
-        // increase size of unique_codes table
-        unique_codes = m_renew(mp_code_t, unique_codes, unique_codes_alloc, next_unique_code_id);
-        for (uint i = unique_codes_alloc; i < next_unique_code_id; i++) {
-            unique_codes[i].kind = MP_CODE_NONE;
+    if (unique_codes_total > unique_codes_alloc) {
+        DEBUG_printf("allocate more unique codes: " UINT_FMT " -> %u\n", unique_codes_alloc, unique_codes_total);
+        // increase size of unique_codes table (all new entries are already reserved)
+        unique_codes = m_renew(mp_code_t, unique_codes, unique_codes_alloc, unique_codes_total);
+        for (uint i = unique_codes_alloc; i < unique_codes_total; i++) {
+            unique_codes[i].kind = MP_CODE_RESERVED;
         }
-        unique_codes_alloc = next_unique_code_id;
+        unique_codes_alloc = unique_codes_total;
     }
 }
 
 void mp_emit_glue_assign_byte_code(uint unique_code_id, byte *code, uint len, int n_args, int n_locals, uint scope_flags, qstr *arg_names) {
     mp_emit_glue_alloc_unique_codes();
 
-    assert(unique_code_id < next_unique_code_id && unique_codes[unique_code_id].kind == MP_CODE_NONE);
+    assert(unique_code_id < unique_codes_alloc && unique_codes[unique_code_id].kind == MP_CODE_RESERVED);
     unique_codes[unique_code_id].kind = MP_CODE_BYTE;
     unique_codes[unique_code_id].scope_flags = scope_flags;
     unique_codes[unique_code_id].n_args = n_args;
@@ -123,7 +134,7 @@ void mp_emit_glue_assign_byte_code(uint unique_code_id, byte *code, uint len, in
 void mp_emit_glue_assign_native_code(uint unique_code_id, void *fun, uint len, int n_args) {
     mp_emit_glue_alloc_unique_codes();
 
-    assert(unique_code_id < next_unique_code_id && unique_codes[unique_code_id].kind == MP_CODE_NONE);
+    assert(unique_code_id < unique_codes_alloc && unique_codes[unique_code_id].kind == MP_CODE_RESERVED);
     unique_codes[unique_code_id].kind = MP_CODE_NATIVE;
     unique_codes[unique_code_id].scope_flags = 0;
     unique_codes[unique_code_id].n_args = n_args;
@@ -154,7 +165,7 @@ void mp_emit_glue_assign_native_code(uint unique_code_id, void *fun, uint len, i
 void mp_emit_glue_assign_inline_asm_code(uint unique_code_id, void *fun, uint len, int n_args) {
     mp_emit_glue_alloc_unique_codes();
 
-    assert(unique_code_id < next_unique_code_id && unique_codes[unique_code_id].kind == MP_CODE_NONE);
+    assert(unique_code_id < unique_codes_alloc && unique_codes[unique_code_id].kind == MP_CODE_RESERVED);
     unique_codes[unique_code_id].kind = MP_CODE_INLINE_ASM;
     unique_codes[unique_code_id].scope_flags = 0;
     unique_codes[unique_code_id].n_args = n_args;
@@ -179,9 +190,9 @@ void mp_emit_glue_assign_inline_asm_code(uint unique_code_id, void *fun, uint le
 #endif
 }
 
-mp_obj_t rt_make_function_from_id(int unique_code_id, mp_obj_t def_args) {
+mp_obj_t rt_make_function_from_id(uint unique_code_id, bool free_unique_code, mp_obj_t def_args) {
     DEBUG_OP_printf("make_function_from_id %d\n", unique_code_id);
-    if (unique_code_id >= next_unique_code_id) {
+    if (unique_code_id >= unique_codes_total) {
         // illegal code id
         return mp_const_none;
     }
@@ -200,8 +211,9 @@ mp_obj_t rt_make_function_from_id(int unique_code_id, mp_obj_t def_args) {
             fun = mp_obj_new_fun_asm(c->n_args, c->u_inline_asm.fun);
             break;
         default:
+            // code id was never assigned (this should not happen)
             assert(0);
-            fun = mp_const_none;
+            return mp_const_none;
     }
 
     // check for generator functions and if so wrap in generator object
@@ -209,13 +221,20 @@ mp_obj_t rt_make_function_from_id(int unique_code_id, mp_obj_t def_args) {
         fun = mp_obj_new_gen_wrap(fun);
     }
 
+    // in some cases we can free the unique_code slot
+    // any dynamically allocade memory is now owned by the fun object
+    if (free_unique_code) {
+        memset(c, 0, sizeof *c); // make sure all pointers are zeroed
+        c->kind = MP_CODE_UNUSED;
+    }
+
     return fun;
 }
 
-mp_obj_t rt_make_closure_from_id(int unique_code_id, mp_obj_t closure_tuple, mp_obj_t def_args) {
+mp_obj_t rt_make_closure_from_id(uint unique_code_id, mp_obj_t closure_tuple, mp_obj_t def_args) {
     DEBUG_OP_printf("make_closure_from_id %d\n", unique_code_id);
     // make function object
-    mp_obj_t ffun = rt_make_function_from_id(unique_code_id, def_args);
+    mp_obj_t ffun = rt_make_function_from_id(unique_code_id, false, def_args);
     // wrap function in closure object
     return mp_obj_new_closure(ffun, closure_tuple);
 }
diff --git a/py/emitglue.h b/py/emitglue.h
index e06578e2205ea063e055ee6a11bf05636e450d93..8f69951c2111878f433ea6d5849a66d38f29def0 100644
--- a/py/emitglue.h
+++ b/py/emitglue.h
@@ -3,7 +3,6 @@
 void mp_emit_glue_init(void);
 void mp_emit_glue_deinit(void);
 uint mp_emit_glue_get_unique_code_id(void);
-uint mp_emit_glue_get_unique_code(uint unique_code_id);
 void mp_emit_glue_assign_byte_code(uint unique_code_id, byte *code, uint len, int n_args, int n_locals, uint scope_flags, qstr *arg_names);
 void mp_emit_glue_assign_native_code(uint unique_code_id, void *f, uint len, int n_args);
 void mp_emit_glue_assign_inline_asm_code(uint unique_code_id, void *f, uint len, int n_args);
diff --git a/py/runtime.h b/py/runtime.h
index b27d12ff1dd0c819f1bbcf217eb79216545c9290..a8f83e529dd1db2c5fda2b8c63c61091f881fdb5 100644
--- a/py/runtime.h
+++ b/py/runtime.h
@@ -18,11 +18,11 @@ void rt_store_global(qstr qstr, mp_obj_t obj);
 void rt_delete_name(qstr qstr);
 mp_obj_t rt_unary_op(int op, mp_obj_t arg);
 mp_obj_t rt_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs);
-mp_obj_t rt_make_function_from_id(int unique_code_id, mp_obj_t def_args);
+mp_obj_t rt_make_function_from_id(uint unique_code_id, bool free_unique_code, mp_obj_t def_args);
 mp_obj_t rt_make_function_n(int n_args, void *fun); // fun must have the correct signature for n_args fixed arguments
 mp_obj_t rt_make_function_var(int n_args_min, mp_fun_var_t fun);
 mp_obj_t rt_make_function_var_between(int n_args_min, int n_args_max, mp_fun_var_t fun); // min and max are inclusive
-mp_obj_t rt_make_closure_from_id(int unique_code_id, mp_obj_t closure_tuple, mp_obj_t def_args);
+mp_obj_t rt_make_closure_from_id(uint unique_code_id, mp_obj_t closure_tuple, mp_obj_t def_args);
 mp_obj_t rt_call_function_0(mp_obj_t fun);
 mp_obj_t rt_call_function_1(mp_obj_t fun, mp_obj_t arg);
 mp_obj_t rt_call_function_2(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2);
diff --git a/py/vm.c b/py/vm.c
index 60ed641a8595155bf016961e6e7734ca7c8620b5..12cea7f26deeaaf9041850dac96fc70fde06e5b1 100644
--- a/py/vm.c
+++ b/py/vm.c
@@ -563,12 +563,12 @@ unwind_jump:
 
                     case MP_BC_MAKE_FUNCTION:
                         DECODE_UINT;
-                        PUSH(rt_make_function_from_id(unum, MP_OBJ_NULL));
+                        PUSH(rt_make_function_from_id(unum, false, MP_OBJ_NULL));
                         break;
 
                     case MP_BC_MAKE_FUNCTION_DEFARGS:
                         DECODE_UINT;
-                        SET_TOP(rt_make_function_from_id(unum, TOP()));
+                        SET_TOP(rt_make_function_from_id(unum, false, TOP()));
                         break;
 
                     case MP_BC_MAKE_CLOSURE: