THis is a really good question,
You then put it in a while loop, and wait for the user to send some code. Exec send the result back.
Here is a 5 lines REPL (read eval print loop) as example on how to evaluate user code interactively.
$ cat repl.py
File: repl.py
1 │
2 │ namespace = dict()
3 │
4 │
5 │ while True:
6 │ code = input('>>> ')
7 │ exec(code, namespace)
$ python repl.py
>>> a = 1
>>> print(a)
1
>>> print(a+1)
2
>>>
You you were to look at namespace, you would see that after a = 1, we have
namespace['a'] == 1, so the namespace dict hold all our variables between exec.
This +/- 40 000 lines of code give you IPython, there is a lot not handled there, but if you take the above, handle exceptions, multiple statements.... etc and forward stdin and out over the network you have a rough prototype of what Jupyter is.
Does that answer your question?
--
Matthias