So, it's worth being aware of how variable scoping works in any program units you are developing.
When the variable i is declared, it is actually hoisted up to the to the top of the function. If you added a statement to the top of the function referencing i, i would have the value of undefined rather than a ReferenceError about using an undeclared variable.
If we introduced this into a language such as Java, we would get a compilation error for trying to use an unknown symbol - that is of course because the variable is only available in that particular block.
So, then, how does PL/SQL behave?
Well, for starters, PL/SQL has a bit more structure to it requiring variables to be declared in the declaration block - as opposed to having the ability to declare anywhere throughout the body of the program (aside from loops where the iterator can be declared inline - `for i in 1..100`).
In saying that, you can declare more variables inline by nesting additional blocks, and those nested blocks will naturally inherit properties declared above them.
The same applies to named sub-units.
If we add a variable to a sub-unit of the same name, then we have a new variable to work with without over-writing the existing variable.
What may be useful, is that we are able access the variable from the parent program unit by prefixing the name or label of the program unit with dot notation when accessing the variable. Be careful here though, if you have a label with the same name as a named program unit, it will use the closest match.
Here, go and have a read of the actual documentation: https://docs.oracle.com/cd/E11882_01/appdev.112/e25519/fundamentals.htm#LNPLS216 and these examples I set up on Oracle Live SQL: https://livesql.oracle.com/apex/livesql/file/content_C4ZGI7BZFVJLLR0YB25SCGN12.html