The boundary between the program and the rest of the system allows I/O of course. What FP does is "virtualize" I/O by representing it as data (thus it can be passed around). Then at some point these changes get "committed" to the outside. Representing I/O separately from how it is carried out allows a lot of things to be done, such as cancelling (ctrl+z) operations.