The unit as writtenunit scope
This is the official scope of the TAFE unit, kept here (folded) so the unit's intended coverage is visible at a glance and my own notes can be placed against it. The notes below are mine; they follow this scope where it still holds and go past it where current practice has moved on.
Unit: ICTPRG302 Apply introductory programming techniques. Nominal hours: 40. A national unit from the ICT training package, used here as an elective alongside the Certificate IV in Cyber Security. The unit describes the skills and knowledge required to create simple applications through introductory programming techniques. No prerequisites, and no licensing or regulatory requirements.
What the unit expects you to be able to do, as six elements. Establish the application task: clarify the task with the people who set it, and identify the design specifications, programming standards and guidelines. Apply language syntax and layout: use basic syntax rules; create code using data types, operators and expressions; apply variables and variable scope; use program library functions; and clarify meaning with comments. Apply control structures: use sequence, selection and iteration, and build expressions in selection and iteration using logical operators. Code using standard programming algorithms: develop algorithms with sequence, selection and iteration; create and use data structures; code the standard sequential access algorithms for reading and writing text files; and apply string manipulation. Test code: examine variable contents and use debugging techniques to detect and correct errors; create and run simple tests against the specification; and document what was done and what the tests showed. Create a simple application and seek feedback: design an algorithm from a basic specification, develop the application, confirm it meets the specification, present it to the people who need it, and obtain feedback and sign off.
Required knowledge. Language data types, operators, expressions and variables; basic language syntax rules; sequence, selection and iteration constructs; the development of small applications; industry programming standards and guidelines; commenting techniques; debugging techniques; application testing methods; and basic data structures.
Performance evidence. The candidate must design and build one simple application according to programming standards and a specification, and in doing so apply syntax and the three control constructs, document changes and tests, and review the code against feedback obtained during design and development.
Assessment conditions. The skills must be shown in a workplace or a simulated environment with the conditions typical of the industry, with access to programming standards and guidelines, programming software, the required hardware, industry-standard software development tools and an integrated development environment.
The delivered version of this unit is taught in Python, with code written to the PEP 8 style guide, using the IDLE editor that ships with Python and the online Python Tutor visualiser for stepping through code. My notes keep Python and PEP 8, and bring the tooling and the security practice up to where the field sits now.
Source: the unit description, elements, performance criteria, knowledge evidence and assessment conditions are taken from the CDU assessor guide for ICTPRG302 (dated 2021, held in the unit folder), which reproduces them from the national unit descriptor on training.gov.au. Nominal hours follow the Victorian Purchasing Guide for the ICT training package. The unit record sits at training.gov.au/Training/Details/ICTPRG302.
Why a security person learns to write code
You can have a long career in cyber security without writing production software. You cannot have one without reading code, because almost everything you defend is code, almost every attack is code, and almost every tool you reach for is a script that someone wrote and that you will end up changing.
So the point of an introductory programming unit, for someone heading into security rather than into software development, is not to turn you into a developer. It is to make code stop being a wall of symbols. Once you can read a short program and say what it does, a great deal opens up: you can understand what a piece of malware is trying to achieve, you can tell whether a pull request quietly introduces a flaw, you can automate the boring half of an investigation, and you can talk to developers as someone who knows what a variable and a loop are.
Python is a good first language for that, which is why the unit chose it and why this page keeps it. It reads almost like structured English, it is the default language of security tooling and data work, and it lets you get a working program running in a few lines without ceremony. Everything in this unit; variables, decisions, loops, data structures, files, functions, testing; is a general idea that exists in every language. Python is just the place you meet each idea first.
Two habits are worth forming from the first program, and both are security habits as much as programming ones. The first is to never trust input, because the person typing into your program may not be the person you imagined. The second is to be able to explain, at any point, what a line of code does and why it is there. Code you cannot explain is code you cannot defend.
Setting up: the interpreter, the editor and the current toolchain
Python comes in two parts that beginners often blur together: the language, and the tools you write it in. Getting the current picture straight here saves confusion later, because this is one of the few parts of an introductory unit where the delivered material has dated.
The interpreter is the program that runs your Python. You install it from python.org, and the current stable version is Python 3.14, released in October 2025; Python 3.15 is due in October 2026. The 3 matters. Python 2 reached end of life on 1 January 2020 and is gone; if you ever find a tutorial where print is written without brackets, it is written for Python 2 and you should close it. Any recent 3.x release is fine for learning, and the differences between 3.11, 3.12, 3.13 and 3.14 will not touch anything in this unit.
The editor is where you type. The unit teaches IDLE, the small editor that installs with Python, and the Wing IDE. IDLE is genuinely fine for your first programs and has the advantage of being already there, so there is nothing to argue with in learning on it. It is worth knowing, though, that almost nobody works in IDLE past the beginner stage. The current industry-standard editor is Visual Studio Code with Microsoft's Python extension, which is free, and the other common choice is PyCharm. The reason to move to one of these before long is not fashion; it is that they give you a proper debugger, they show you errors as you type, and they integrate the formatting and linting tools described below. Learn the ideas in IDLE if that is what is in front of you, then expect to outgrow it.
Definition
IDE (integrated development environment): an editor bundled with the other tools you need to build software; a way to run code, a debugger to step through it, and usually a linter and formatter. IDLE, Wing, VS Code and PyCharm are all IDEs of increasing capability.
Two ideas from the current toolchain are worth meeting now, even though the unit predates them, because they change what "writing to a style guide" means in practice. A formatter rewrites your code into a consistent style automatically; Black and Ruff are the two you will meet, and they mean nobody argues about spacing any more because the tool settles it on save. A linter reads your code without running it and flags likely mistakes and style breaches; Ruff, again, is the current default and is fast enough to run as you type. The practical upshot is that PEP 8, which the unit asks you to apply by hand, is now mostly applied for you by a tool, and the skill has shifted from remembering the rules to knowing why they exist and reading what the tool tells you.
The last piece is a tool the unit uses well and which has not dated at all. Python Tutor runs a short program in your browser and draws what happens in memory at each step: which variables hold what, how a loop advances, how a function call stacks up. For understanding scope, iteration and data structures, it is one of the best things on the internet, and it is used again in several sections below.
From task to plan: reading a specification before writing code
The first element of the unit is not about code at all. It asks you to clarify the task with the people who set it, and to identify the specifications, standards and guidelines before you start. That ordering is deliberate, and it is the single habit that most separates people who finish programs from people who get stuck.
The instinct of a beginner is to start typing. The instinct worth building is to first answer three questions in plain language. What exactly is this program supposed to do, stated as inputs and outputs? What counts as done, so that you know when to stop? And what could the user do that you have not planned for? A specification that says "validate a password" is not finished until it says how long the password must be, which kinds of character it must contain, what message the user sees when it fails, and what happens when they enter nothing at all.
Only once that is clear does an algorithm get written, and it gets written before the code, in plain words or pseudocode. An algorithm is just the steps, in order, that solve the problem. Writing it first means you are solving the problem while thinking about the problem, rather than solving it while also fighting the syntax. A first algorithm for a password check might read: ask the user for a password; if it is empty, stop; if it is shorter than the minimum, tell them and ask again; otherwise check it contains the required kinds of character, and either accept it or tell them exactly what is missing.
Definition
Pseudocode: the steps of a program written in structured English rather than in a real language, so the logic can be checked before any syntax gets in the way. It is not run by anything; it is a thinking tool.
The delivered unit places this planning inside an agile framing, with a short stand-up discussion and a small piece of sprint documentation recording the goal, the method and how the result was tested. That is worth taking seriously rather than treating as paperwork, because it is how real teams work.
Definition
Agile, scrum and a sprint: agile is a way of building software in short cycles with frequent feedback rather than one long march to a deadline. A sprint is one such cycle. A scrum, or stand-up, is a brief regular meeting where each person says what they are doing and what is blocking them. The documents you keep are light on purpose; enough to record what you set out to do and whether you achieved it.
The reason this matters for a security reader is that most vulnerabilities are not clever; they are cases the author did not think about. The empty input, the negative number, the name with an apostrophe in it, the file that is not there. The discipline of writing down what the program must handle, before writing the program, is the same discipline that stops those cases becoming holes.
Values, variables and data types
A program is mostly moving values around and making decisions about them, so the first real skill is knowing what kinds of value there are and how to hold on to one.
A variable is a name that refers to a value. In Python you make one just by assigning to it, and you do not declare a type first:
attempts = 0
username = "root"
locked_out = False
The name on the left refers to the value on the right. A useful way to picture it, and the way Python Tutor draws it, is that the value sits in memory and the name is a label pointing at it. Reassigning the name just moves the label; attempts = attempts + 1 computes a new value and repoints the label.
The core data types you need for this unit are few. Whole numbers are int. Numbers with a decimal point are float. Text is str, written in single or double quotes. Truth values are bool, either True or False. And the absence of a value is None, a real value that means "nothing here yet". Everything else you meet early is built from these.
Definition
Data type: the kind of a value, which decides what you can do with it. You can add two
intvalues and get their sum; add twostrvalues and you get them joined end to end; try to add anintto astrand Python stops you, because it will not guess what you meant.
Python is dynamically typed, which means a variable's type is decided by whatever value it currently holds, and the interpreter checks types as it runs rather than before. This is why Python feels forgiving; it is also why a whole class of mistakes only shows up when the line actually runs. The most common beginner trap follows directly from it: input read from a user always arrives as a string, so the classic age = input("Age: ") gives you the text "40", not the number 40, and age + 1 then fails. You convert explicitly with int() or float().
Did you know?
That "input is always a string" trap is not just a beginner annoyance; it is the seam where a lot of security bugs live. A program that assumes input is a number, and does not check, is a program an attacker can feed something that is not a number. Converting input, and handling the case where the conversion fails, is defensive from the very first program.
Operators are the verbs. Arithmetic is + - * /, with // for whole-number division, % for the remainder, and ** for powers. The remainder operator earns its keep more than beginners expect: n % 2 == 0 is how you test whether a number is even, and modular arithmetic like this is the arithmetic underneath a lot of cryptography. Comparison operators, == != < <= > >=, produce a bool, and the double equals for "is equal to" versus the single equals for "assign" is worth burning in early, because confusing them is a rite of passage.
A modern addition worth adopting, though the unit predates it, is the type hint. You can annotate what a variable or function is meant to hold:
attempts: int = 0
def is_valid(password: str) -> bool:
...
Python does not enforce these at run time; they are for the reader, for the editor's autocomplete, and for tools that check your code without running it. In current professional Python they are close to standard, and starting with them is a good habit rather than an advanced topic.
Variable scope
Scope is the answer to a question beginners hit as soon as they write their first function: where can this variable be seen? The unit calls it out specifically, and it is worth understanding rather than memorising, because getting it wrong produces bugs that look like magic.
A variable created inside a function is local to that function. It exists while the function runs and vanishes when the function returns, and code outside the function cannot see it. A variable created at the top level of your file is global, and functions can read it. This is mostly what you want: it means a function is a sealed box, and the names you use inside one function cannot collide with the names inside another.
def check(password):
length = len(password) # local: exists only inside check
return length >= 8
length = 20 # a different, global 'length'
print(check("short"))
print(length) # still 20; the function's length was separate
The rule Python follows when it looks up a name is worth knowing by its initials, LEGB: it looks in the Local function first, then any Enclosing function, then the Global level of the file, then the Built-ins. The practical advice that falls out of this is simple: keep variables as local as you can. A function that takes what it needs as parameters and returns its result, without reaching out to global variables, is easier to test, easier to reason about and far less likely to surprise you. Reaching into globals from inside functions, and especially reassigning them, is where scope bugs breed. This is one of the things Python Tutor draws beautifully; step through the snippet above and watch the two length values sit in separate frames.
Making decisions: selection
Selection is how a program chooses between paths, and with sequence and iteration it is one of the three constructs the whole unit is built on. The idea is small; the discipline around it is where the value is.
The basic form is if, optionally with elif for further tests and else for the fallback:
if len(password) < 8:
print("Too short.")
elif password == password.lower():
print("Add an uppercase letter.")
else:
print("Looks fine.")
Python decides which branch to run using the truth of the condition, which must work out to True or False. Conditions are built with the comparison operators from earlier and joined with the logical operators and, or and not, which the unit names specifically. and is true only when both sides are; or is true when either side is; not flips a truth value. Getting the logic right is often more about being precise than about syntax: "the password is valid if it is long enough and has an uppercase and has a digit" is three conditions joined with and, and if you write or by mistake the program will happily accept a password that meets only one of the three.
Two Python-specific points save real confusion. Indentation is not decoration; it is the syntax. The lines belonging to an if are the lines indented under it, and getting the indentation wrong changes the meaning or stops the program running, where other languages use brackets. And Python has a notion of truthiness: an empty string, an empty list, the number zero and None all count as false in a condition, while non-empty and non-zero values count as true. This is why if password: is a common and readable way to say "if the user actually typed something", and it is worth recognising when you read other people's code.
Repeating work: iteration
Iteration is doing something more than once, and it is the construct that makes a computer worth having, because the whole point is to make it do the repetitive thing so you do not have to.
Python gives you two loops. A for loop runs once for each item in a collection:
for character in password:
if character.isdigit():
print("Contains a digit.")
A while loop runs as long as a condition stays true, which is what you want when you do not know in advance how many times to go around:
while True:
password = input("Create a password: ")
if password == "":
break # leave the loop
if is_valid(password):
print("Accepted.")
break
print("Try again.")
for is the right choice when you are working through the items of something; a string character by character, a list element by element. while is the right choice when you are repeating until a condition changes; keep asking until the input is valid, keep going until the user quits. The break statement leaves a loop early, and continue skips to the next turn of the loop. The classic beginner bug is the infinite loop, a while whose condition never becomes false, usually because you forgot to change the thing the condition depends on. When a program hangs, an infinite loop is the first suspect.
The range() function is how you loop a set number of times: for i in range(3) runs with i taking the values 0, 1 and 2. That it starts at 0 and stops before the number you give is a convention worth internalising early, because counting from zero is everywhere in programming, and off-by-one errors, going around one time too many or too few, are among the most common mistakes anyone makes at any level.
flowchart TD
A[Start] --> B{More characters<br/>to check?}
B -- yes --> C[Look at next character]
C --> D{Is it a digit?}
D -- yes --> E[Record: has a digit]
D -- no --> B
E --> B
B -- no --> F[Report result]
The diagram is one loop with a decision inside it, which is most of what real programs are: sequence, running top to bottom; selection, the diamond; and iteration, the arrow that goes back up. Almost everything you build is those three arranged and nested.
Grouping data: the core data structures
A single variable holds one value. Real programs need to hold many values at once; a list of log entries, a set of blocked addresses, a record of a user. The unit asks for "basic data structures", and in Python that means four, each suited to a different shape of problem.
A list is an ordered, changeable collection, written in square brackets. It is the workhorse:
failed_ips = ["10.0.0.4", "10.0.0.9"]
failed_ips.append("192.168.1.7") # lists can grow
print(failed_ips[0]) # index from zero: "10.0.0.4"
A tuple is an ordered collection that cannot be changed after it is made, written in round brackets. You use one when the group is fixed; a coordinate, a database row, a pairing that should not be edited by accident. The un-changeability is the feature, not a limitation.
A dictionary is a collection of key-and-value pairs, written in curly brackets, and it is the one that changes how you think. Instead of finding a value by its position, you find it by a meaningful key:
user = {"name": "sam", "role": "analyst", "active": True}
print(user["role"]) # "analyst"
A dictionary is the natural way to hold a record, and looking a value up by key is fast no matter how large the dictionary grows, which matters once you are dealing with real volumes of data. A set is an unordered collection with no duplicates, which makes it exactly right for questions of membership: "have we seen this address before?" is a fast question to ask of a set, and adding an address that is already there simply does nothing.
Definition
Mutable and immutable: a mutable value can be changed in place after it is created; an immutable one cannot. Lists, dictionaries and sets are mutable; strings, numbers and tuples are immutable. This distinction matters more than it first appears, because passing a mutable value into a function lets the function change your original, while passing an immutable one does not.
Choosing the right structure is most of what makes a program clear. If you find yourself keeping several parallel lists and lining them up by position, you almost certainly want a list of dictionaries instead. If you are repeatedly scanning a list to ask whether something is in it, you want a set. The structures are not interchangeable; each answers a particular kind of question quickly and the others slowly.
Working with text: string manipulation
Strings get their own element in this unit, and they deserve it, because in security work text is most of what you handle: log lines, headers, tokens, user input, file contents. Being fluent with strings is a large part of being useful.
A string is a sequence of characters, and you can reach into it by position. Indexing uses square brackets and counts from zero, so password[0] is the first character. Slicing takes a range, password[0:3] giving the first three characters, and negative indices count from the end, so password[-1] is the last character. That last trick is why the delivered password example ends with message[:-2], which trims the trailing comma and space off a message it has been building up; slicing from the start to two before the end.
Strings come with a large set of methods, functions attached to the value, and the useful ones read almost like English: .lower() and .upper() change case, .strip() removes surrounding whitespace, .startswith() and .endswith() test the ends, .find() locates a substring, .replace() swaps one piece of text for another, and .split() breaks a string into a list on a separator. A single method call often replaces a loop you were about to write. Testing a character's nature is common enough to have its own methods too: .isdigit(), .isupper() and .islower() are exactly what the password example uses to decide what a password contains.
Joining text together, concatenation, can be done with +, but the current and far more readable way is the f-string, a string with an f before the opening quote and expressions in curly brackets:
name = "sam"
attempts = 3
print(f"{name} has {attempts} attempts left.")
f-strings arrived in Python 3.6 and are now the normal way to build text; the older + joining and the even older % formatting still work and still appear in code you will read, but there is no reason to reach for them in new code.
Did you know?
Almost every classic web vulnerability is, underneath, a string-handling mistake. SQL injection is a query built by gluing user input into a string with
+. Cross-site scripting is a web page built the same way. The habit of never building a sensitive string out of raw user input, and instead using the mechanism designed to keep data and code separate, starts here in an introductory unit and runs all the way through the security syllabus.
Functions and the standard library
A function is a named, reusable piece of program. You have been calling functions since your first line; print() and input() and len() are functions someone else wrote. Writing your own is how a program stops being one long script and becomes a set of parts you can name, test and trust.
def is_valid(password):
if len(password) < 8:
return False
has_upper = any(c.isupper() for c in password)
has_digit = any(c.isdigit() for c in password)
return has_upper and has_digit
The pieces to name: def starts a definition; password is a parameter, the input the function is given; return hands a value back to whoever called the function and ends it. A function that returns its answer, rather than printing it, is far more useful, because the caller can then decide what to do with the answer; print it, store it, test it, act on it. The single most valuable habit with functions is that each one should do one clearly nameable thing. If you cannot name a function without using "and", it is probably two functions.
Functions pay off in three ways worth being explicit about. They remove repetition, so a change is made in one place rather than many; the principle has a name, "don't repeat yourself", and it is a security property as much as a tidiness one, because duplicated logic is where one copy gets fixed and the other does not. They make code readable, because a well-named function call tells the reader what happens without the how. And they make code testable, because a function with clear inputs and outputs can be checked on its own.
Beyond your own functions sits the standard library, a large collection of ready-made functionality that ships with Python, reached with import. The unit calls this "program library functions". A few you meet early: math for mathematics beyond the basic operators, random for random numbers, datetime for dates and times, and os for talking to the operating system. The reflex to build here is to look before you write; if something feels common, the standard library probably already does it, and the version in the library is tested and correct in ways your first attempt will not be.
Did you know?
This reflex has a sharp security edge in the current era. Beyond the standard library sits the vast world of third-party packages installed with
pip, and reaching for one of those is where an introductory habit meets a live supply-chain risk. A package you did not check, or one whose name an AI assistant invented, can carry malicious code straight into your project. The secure-coding and AI sections below come back to this; for now, the point is that "use a library rather than reinvent it" is good advice that comes with a "know what the library is" attached.
Reading and writing files
A program that forgets everything when it closes is limited; most useful programs read something in or write something out. The unit asks specifically for the standard pattern of reading and writing text files sequentially, and it is worth learning the current form of it rather than the older one.
The idea is a three-step sequence: open the file, work with it, close it. Opening returns a file object and takes a mode; "r" to read, "w" to write and replace, "a" to append to the end. The delivered material opens and closes files by hand:
secret = open("secrets.txt", "a")
secret.write(text + "\n")
secret.close()
That works, and you should be able to read it, because a great deal of existing code is written this way. But it has a flaw that matters: if anything goes wrong between opening and closing, the close() never runs and the file is left open. The current standard way removes that risk with a with block, which guarantees the file is closed even if an error occurs partway through:
with open("secrets.txt", "a") as secret:
secret.write(text + "\n")
# the file is closed automatically here, error or not
This is not an advanced refinement to reach for later; it is simply how files are opened in current Python, and a good habit to start with. Reading is the mirror image: with open("log.txt", "r") as f: and then either f.read() for the whole thing at once, or, better for large files, a for line in f: loop that reads one line at a time and never has to hold the whole file in memory.
Did you know?
Files are the first place an introductory program touches the outside world, and the outside world is where security lives. Two habits belong here from the start. A file path built out of user input is a path an attacker can point somewhere you did not intend, the classic "path traversal" bug. And a file full of secrets, like the
secrets.txtin the delivered example, is a real thing in real systems; the fact that a beginner exercise writes secrets to a plain text file next to the program is itself a lesson in what not to do in production, where secrets belong in a managed store, not a file in the project folder.
Comments, style and the standards that carry your name
The unit asks you to clarify code with comments and to work to industry programming standards and guidelines. It is easy to treat this as the box-ticking part of the unit. It is closer to the opposite: style and comments are how code survives contact with other people, and in security almost all code is read by other people, often years later, often while something is on fire.
A comment is a note to a human, ignored by the interpreter, marked with #. The skill in commenting is knowing what to say. A bad comment restates the code; x = x + 1 # add one to x helps nobody. A good comment explains why, the thing the code cannot say for itself; # retry once, because the auth server drops the first request after a restart. The best code needs few comments because the names carry the meaning: a function called is_valid and a variable called failed_attempts explain themselves, where f and n do not.
A docstring is a special comment, a string just inside a function, that describes what the function does; tools and editors show it to anyone using the function, so it is documentation that travels with the code.
The standard the unit names is PEP 8, the style guide for Python, at peps.python.org/pep-0008. It settles the small questions so a team does not have to: four spaces for indentation, lower_case_with_underscores for variable and function names, spaces around operators, a limit on line length, and so on. Its own most-quoted line is the point of the whole thing: code is read far more often than it is written, so consistency is a kindness to the next reader, who is often you.
The part that has moved since the unit was written is who applies PEP 8. The delivered assessment asks students to point to places where their code follows the guide by hand. In current practice, as noted in the setup section, a formatter like Black or Ruff applies most of PEP 8 automatically on save, and a linter flags the rest. This does not make the guide irrelevant; it makes the human job the more interesting half. Knowing why a rule exists, reading the messages the linter gives you, and handling the judgement calls a formatter cannot make, like whether a name is actually meaningful, is the skill now. Learn the rules so you understand the tool, then let the tool do the mechanical part.
Finding out why it broke: debugging and testing
Everyone's code breaks, constantly, at every level of experience. The difference between a beginner and a professional is not that the professional's code breaks less; it is that the professional is calm and systematic about finding out why. The unit gives testing and debugging their own element, and it is arguably the most transferable thing in the whole course.
Start by naming the three kinds of error, because the fix is different for each. A syntax error means the code is not valid Python and will not run at all; a missing bracket, a misspelt keyword. The interpreter refuses before it starts, and the fix is usually where it points. A runtime error, or exception, means the code started running and then hit something it could not do; dividing by zero, converting the word "cat" to a number, opening a file that is not there. Python stops and prints a traceback. A logic error is the worst of the three, because the program runs perfectly and produces the wrong answer; the code does what you told it, and what you told it was wrong.
Reading a traceback is a skill in itself, and a calming one once you have it. The most useful line is the last one, which names the type of error and gives a short message; the lines above show the path the program took to get there, most-recent at the bottom. Beginners' eyes slide off tracebacks as a wall of red. Train yourself to read the bottom line first; it is usually telling you exactly what went wrong.
For finding the cause, three tools in increasing order of power. The humble print() is where everyone starts and it is not to be sneered at; putting a print() inside a loop to see the value of a variable each time around answers most questions. The debugger built into a proper editor is the grown-up version; it lets you pause the program on a line and inspect every variable at that moment, and stepping through a confusing function once in a debugger teaches more than an hour of staring. And for pure understanding, Python Tutor draws the same thing visually, which is why the unit uses it; it is the gentlest on-ramp to seeing what your code actually does rather than what you think it does.
Testing is the other half, and it is the discipline of checking that code does what the specification said, not just that it runs. At its simplest a test is an assert; a line that states what should be true and complains loudly if it is not:
assert is_valid("Abcdefg1") == True
assert is_valid("short") == False
The current professional form of this is a testing framework, and pytest is the one to know; it lets you write a file full of small test functions and run them all at once, so that after any change you can confirm in seconds that you have not broken anything that used to work. The unit does not require pytest, and you do not need it to pass, but the idea it embodies is the one the unit is teaching: decide what "correct" means, write it down as checks, and run the checks. Documenting what you tested and what you found, which the unit also asks for, is not bureaucracy; it is the record that lets the next person trust your code without redoing your work.
Did you know?
In security, testing has a mirror image called the negative test; instead of checking that valid input works, you check that invalid and malicious input fails safely. The password validator that accepts every good password is only half tested; the interesting tests are the empty string, the thousand-character input, the string full of spaces, the input that is not a string at all. The mindset that asks "how could this go wrong?" is the same mindset that finds vulnerabilities, and it starts in the testing section of an introductory unit.
Building a small application end to end
The final element asks you to pull the whole unit together: take a basic specification, design an algorithm, build the application, confirm it meets the specification, present it and get sign-off. This is where the separate skills stop being separate. It is worth seeing the shape of that arc in one place, because it is the shape every program you ever write will follow, from a five-line script to a system.
flowchart LR
A[Clarify the<br/>specification] --> B[Design the<br/>algorithm]
B --> C[Write the<br/>code]
C --> D[Test against<br/>the spec]
D -- fails --> E[Debug]
E --> C
D -- passes --> F[Review and<br/>get feedback]
F --> A
Read that as a loop, not a line. You clarify, design, write and test; when a test fails you debug and go back to the code; and when it passes you seek feedback, which very often sends you back to clarifying the specification, because showing someone a working program is the fastest way to discover what they actually wanted. The delivered unit builds three small programs across its assessment, and each one exercises a different slice of this: one is about building to a specification and documenting the plan, one is about being handed broken code and debugging it back to correctness, and one is about finishing a partly written program made of several files that work together. That last shape, a program split across files that import from each other, is worth dwelling on for a moment, because it is where an introductory unit touches how real software is actually organised.
A program of any size is not one long file; it is several modules, each holding related functions, importing what they need from each other. One file drives the program, another holds the functions that do a particular job, another handles reading and writing. The value is the same value functions give you, one level up: each file has one responsibility, can be understood on its own, and can be tested on its own. When you meet a real codebase, whether to defend it or to change it, this is what you find; not a wall of code, but a set of named parts with a flow between them. Being able to open such a thing and trace how a value moves from the file that reads it, through the file that transforms it, to the file that writes it out, is a large part of what this whole unit is quietly training you to do.
Presenting the finished program and getting sign-off is the last step, and it is not a formality. Explaining your code out loud to someone else is the fastest way to find its weaknesses; the bug you cannot see on the screen becomes obvious the moment you have to describe the line to a human. And feedback is not criticism to endure; in the code review section of professional life, the second pair of eyes is the main defence against the mistake the author cannot see. A person who takes feedback on their code well, and gives it kindly, is a person teams want.
Writing code that does not become tomorrow's vulnerability
This section is not in the unit, and it is the most important one on the page for a security reader. An introductory programming unit teaches you to make code work. It says almost nothing about making code safe, because it was written to teach programming rather than security. But the three small programs this unit builds, a password checker, a piece of list processing, and a thing that encrypts and stores text, sit on top of exactly the questions that a security professional spends a career on. So it is worth teaching the current, secure version of each idea alongside the introductory one, without which a beginner learns habits they will later have to unlearn.
Start with input, because it is the foundation. The single most useful security principle in all of programming is that all input is guilty until proven innocent. Anything that comes from outside your program, what the user types, what a file contains, what arrives over the network, is under someone else's control and may be nothing like what you expected. Validating input, checking it is the right type, the right length and the right shape before you act on it, and handling the case where it is not, is defensive from the first program. The password validator in this unit is, seen this way, a small lesson in input validation.
Then the password itself, because the delivered example teaches something the industry has actually moved away from. The unit's validator enforces composition rules: at least one uppercase, one lowercase, one digit. That was the mainstream advice for years, and it is exactly the advice that current guidance now cautions against. The reasoning is that composition rules push people towards predictable patterns like "Password1!" without adding much real strength, while punishing memorable long passphrases. The current position, set out in the United States by NIST's Digital Identity Guidelines (SP 800-63B) and echoed by the Australian Signals Directorate, is that length is what matters most, that long passphrases should be allowed and encouraged, that passwords should be checked against lists of known-breached values rather than forced through composition rules, and that arbitrary periodic expiry does more harm than good. Building the composition-rule validator is a fine way to learn loops and strings; knowing that current practice has moved past it is the part the unit cannot tell you.
And never, ever, store a password as text. This is the lesson hiding inside the exercise, and it is worth stating plainly because the exercise does not. A real system never keeps the actual password. It keeps a hash, the output of a one-way function that turns the password into a fixed fingerprint that cannot be reversed, computed with a slow, salted algorithm made for the job, such as bcrypt, scrypt or Argon2. When the user logs in you hash what they typed and compare fingerprints; the real password is never stored and cannot be stolen from your database because it was never there.
Definition
Hashing, encryption and encoding, which are not the same thing: encoding, like Base64, just changes a value's representation and can be undone by anyone; it is not a security control. Encryption scrambles a value with a key so that someone with the key can unscramble it; it is two-way by design. Hashing runs a value through a one-way function that cannot be reversed; you can check whether something matches, but you cannot get the original back. Passwords are hashed. Data you need to read again is encrypted. Confusing the three is one of the most common mistakes beginners make, and it has real consequences.
That distinction lands directly on the third program this unit builds, the one that "encrypts" text with a pair of scrambled character strings and a shift. It is a substitution cipher, and it is a wonderful exercise for learning strings, loops and file handling; it teaches real skills and there is nothing wrong with building it. But it is not encryption in any security sense, and it is worth knowing why, because the reasoning is the beginning of cryptographic literacy. Its secret is baked into the program where anyone who reads the code can see it; it has no key that could be kept separately; and a fixed substitution preserves the patterns of the original text, which is exactly what makes such ciphers trivial to break. The lesson is the one every cryptographer states first: do not invent your own cryptography. Real systems use vetted, standard algorithms through vetted libraries, because the security of a cipher rests on decades of expert attack, not on the cleverness of its author. A homemade cipher that has not been broken has usually just not been looked at.
Two more habits belong here, briefly, because they start at this level. Do not hardcode secrets; a password, an API key or a token written into the source code is a secret published to everyone who ever sees that code, and the fact that it is committed to a repository is one of the most common real-world leaks. And give a program only the access it needs, the principle of least privilege; a script that only reads a file does not need permission to delete it, and a program that runs with more power than it requires is a bigger prize when it is compromised. None of this is advanced. All of it is habit, and habits are easiest to form at the start, which is exactly why an introductory unit is the right place to meet them.
Programming with AI assistance
Nothing has changed introductory programming as much, as fast, as AI coding assistants, and the unit says nothing about them because there was nothing to say when it was written. This is the largest gap in its scope, and closing it honestly matters, because the way a person learns to code in 2026 is genuinely different from the way the unit assumes, and the difference cuts both ways.
The tools first, plainly. An AI coding assistant is a large language model trained on vast amounts of code that will, given a description or a few lines of context, produce code for you. You meet them as autocomplete that finishes your line or writes a whole function, as GitHub Copilot and similar tools built into the editor; as a chat window you describe a problem to; and, increasingly, as an agent that can write, run and change code across a whole project on its own. They are genuinely useful. They flatten the early frustration of syntax, they explain error messages in plain language, they turn "how do I read a file in Python" from a search into an answer, and used well they let a beginner spend more time on the thinking and less on the fighting.
The honest risks matter more for a security reader, and there are four worth naming.
The first is that generated code is often wrong in ways that run. A model produces code that looks right and executes, but does the wrong thing on the input you did not mention, or handles the happy path and ignores the error case. This is exactly the logic error from the debugging section, arriving faster and with more confidence than before. The defence is unchanged: you own the code, so you test it against the specification and you can explain every line, whether you typed it or a model did.
The second is a supply-chain risk specific to AI, and it is the sharpest reason this belongs on a security site. Models invent packages that do not exist. Asked to solve a problem, an assistant will sometimes tell you to install a library with a plausible name that has never existed, because the name is a statistical guess rather than a fact. A study presented at the USENIX Security Symposium in 2025, "We Have a Package for You!", tested sixteen code-generating models and found that around one in five of the packages they recommended did not exist, roughly 5 per cent for the commercial models and around 21 per cent for the open-weight ones, adding up to more than 200,000 unique hallucinated package names; and, worse, that the same false names came back consistently, with about 45 per cent of hallucinations reappearing every time the same prompt was run. That consistency is what turns a mistake into an attack. Someone can watch for the invented names, register a malicious package under one of them, and wait for the next person who trusts an assistant's suggestion to install it. The practice has a name now, slopsquatting, and it is a live technique rather than a theoretical one. The defence is simple and non-negotiable: never install a package because a model named it. Check that it exists, that it is the one you think it is, that real people maintain it and use it, before it comes anywhere near your project.
Definition
Slopsquatting: registering a malicious software package under a name that AI assistants tend to hallucinate, so that developers who copy the assistant's suggestion install the attacker's code. A close relative of typosquatting, but the bait is an AI's invention rather than a human's typo.
The third is over-reliance, and it is the one that matters most while you are still learning. A tool that removes the struggle of writing code also removes the learning that the struggle produces. If the assistant writes every loop, you do not learn to write a loop, and the day the assistant is wrong, which it will be, you cannot tell. The way to learn with these tools rather than around them is to use them as a tutor, not a ghostwriter: write the hard part yourself, ask the assistant to explain rather than to produce, have it review your code and tell you why a line is weak, and treat any code it gives you as a draft by a fast, confident colleague who is sometimes wrong and never liable. You are liable.
The fourth is confidentiality, and it connects straight back to the legislation and privacy side of security. Whatever you paste into a consumer AI tool leaves your control and may be logged, retained or used to train the next model. Client code, real data, secrets and keys do not go into a public assistant, for the same reason you would not email them to a stranger. Where AI assistance is genuinely wanted in real work, it belongs in a tool the organisation has assessed and approved, with its retention and training settings understood.
flowchart TD
A[Describe the problem<br/>to the assistant] --> B[Read the code it produced]
B --> C{Do I understand<br/>every line?}
C -- no --> D[Ask it to explain,<br/>or write it myself]
D --> B
C -- yes --> E{Does it name a<br/>package I do not know?}
E -- yes --> F[Verify the package<br/>is real and trusted]
F --> G
E -- no --> G[Test it against<br/>the specification]
G -- fails --> D
G -- passes --> H[Keep it; I own it now]
The through-line of this whole page is in that diagram, and it is the same discipline the testing and secure-coding sections asked for. Whether a line of code came from your head, a textbook, an old deck or a model, the questions are identical: do I understand it, is it safe, does it do what the specification says, and can I stand behind it. AI changes how fast the code arrives. It does not change whose responsibility it is once it is in your program. That responsibility is the actual subject of an introductory programming unit, and it is the one thing no tool will ever take off your hands.
Sources used
The unit's scope, elements, performance criteria, knowledge evidence and assessment conditions are taken from the CDU assessor guide for ICTPRG302 (dated May 2021, held in the unit folder), which reproduces the national unit descriptor published at training.gov.au; nominal hours follow the Victorian Purchasing Guide for the ICT training package. The current Python version and release dates are from the Python developer's guide status page (devguide.python.org/versions, read September 2026) and python.org; Python 2's end-of-life date is from the Python Software Foundation. PEP 8 is the Python style guide at peps.python.org/pep-0008; the formatter and linter noted are Black and Ruff. The code-visualisation tool is Python Tutor. The password guidance reflects the United States NIST Digital Identity Guidelines SP 800-63B and the Australian Signals Directorate's advice on passphrases, both current as at 2026. The package-hallucination findings and figures are from Spracklen and colleagues, "We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs", USENIX Security Symposium 2025 (usenix.org); the term "slopsquatting" was coined by Seth Larson in 2025. Tooling directions for editors follow the current market position of Visual Studio Code with Microsoft's Python extension. Where this page describes practice as still moving, in particular AI-assisted coding and its risks, it is written as the live and contested picture it is as at September 2026, and is worth re-checking against current sources before it is relied on.