Google IT Automation with Python study materials
Google IT Automation with Python study resources

Google IT Automation with Python common mistakes

Google IT Automation with Python · Understand the traps and how to avoid them.

Mistake 1

Writing a loop that mutates the list it is iterating over (e.g., calling .remove() inside a for loop) and expecting every element to be visited.

Indices shift as elements are deleted, so the loop silently skips elements; beginners reason about the loop as if it snapshots the list. This shows up in file-cleanup and log-filtering scripts where candidates delete matching lines in place.

What to do instead

Build a new list with a comprehension (kept = [x for x in items if not should_drop(x)]) or iterate over a copy (for x in list(items)). When filtering files, collect paths first, then act on the collected list.

Mistake 2

Confusing a function that returns a value with one that prints it, then assigning the result of print() or sort() and getting None.

Interactive experimentation makes printed output look like a returned value, and .sort() versus sorted() is an easy trap. Candidates then call methods on None and hit AttributeError.

What to do instead

Memorize the mutating-in-place versus returning-copy pairs: list.sort() returns None while sorted() returns a new list; same for .reverse()/reversed() and dict.update(). Add a return statement to any helper whose result you plan to use, and test helpers by asserting on the return value, not on printed output.

Mistake 3

Opening files without a context manager or without specifying an encoding, then reading a file expecting a list of lines but getting one string (or vice versa).

Candidates learn open()/read() first and never adopt the with pattern; on systems whose default encoding is not UTF-8, reading a log with non-ASCII bytes raises UnicodeDecodeError. read() returns one string while readlines()/iteration yields lines, and candidates mix them up when parsing.

What to do instead

Always use with open(path, encoding='utf-8') as f: and choose deliberately between f.read() (whole string), f.read().splitlines() (list without newlines), and iterating f (lines with trailing newlines). Strip with .rstrip('\n') before comparing.

Mistake 4

Assuming relative paths resolve from the script's location, or building paths by string concatenation with '/' instead of os.path.join.

Scripts tested from the project directory work, then break when run from cron or another working directory. Hand-built paths also produce doubled separators or break on Windows.

What to do instead

Resolve paths explicitly: use pathlib.Path(file).resolve().parent for script-relative files, os.getcwd() when the working directory is intended, and os.path.join or the / operator on Path objects for composition. Print the resolved absolute path when debugging file-not-found errors.

Mistake 5

Running subprocess calls with shell=True and an interpolated string, or forgetting that subprocess.run returns a CompletedProcess whose output must be captured.

shell=True is the shortest way to make a pipeline work, so it gets copied everywhere; it also means filenames with spaces or shell metacharacters break or execute unintended commands. Candidates also expect run() to return stdout as a string and are surprised by a CompletedProcess object.

What to do instead

Pass an argument list with shell=False: subprocess.run(['du', '-sh', path], capture_output=True, text=True, check=True) and read result.stdout. Use check=True to raise on non-zero exit codes, and reserve shell=True for genuine shell syntax with fully controlled input.

Mistake 6

Killing the wrong process or assuming a PID is stable: calling os.kill on a PID captured earlier without verifying the process still matches, or using os.system('kill ...') instead of the subprocess/os APIs.

PIDs are recycled, so a stale PID can target an unrelated process; candidates also confuse the shell's job control with Python's process APIs and forget that signals other than SIGKILL allow cleanup.

What to do instead

Look up processes by inspecting /proc//cmdline or psutil, re-verify identity immediately before signalling, and prefer SIGTERM first, escalating to SIGKILL only after a timeout. Use subprocess.run(['kill', '-TERM', str(pid)]) or os.kill(pid, signal.SIGTERM) rather than string-built shell commands.

Mistake 7

Committing generated or sensitive files because .gitignore was written after the files were already tracked, and expecting .gitignore to untrack them.

Candidates add .gitignore late and assume it retroactively applies; Git ignores rules only for untracked files, so previously committed artifacts and secrets keep appearing in diffs and history.

What to do instead

Untrack with git rm --cached (or -r for directories), commit the removal, then confirm with git status. Add the .gitignore entry in the same commit and verify with git check-ignore -v . If a secret was pushed, treat it as compromised and rotate it, not just delete the file.

Mistake 8

Merging or rebasing a shared branch locally and then pushing without understanding divergence, producing rejected pushes and accidental merge commits in pull requests.

Candidates treat pull as a sync button and rebase feature branches that others already pulled, rewriting shared history. They also merge the base branch into the feature branch instead of updating it, cluttering the PR diff.

What to do instead

Before pushing, run git fetch and inspect with git status and git log --oneline --graph. Rebase only your own unpushed commits (git rebase origin/main); merge for shared branches. If a push is rejected, pull with --rebase only when the remote commits are yours, otherwise merge, and never force-push a shared branch without --force-with-lease and team agreement.

Mistake 9

Writing automation scripts with no tests or only happy-path tests, so error handling paths (missing file, permission denied, non-zero exit code, network timeout) are never exercised.

Candidates test by running the script once against real data and seeing it succeed; writing tests feels slower than manual runs, and mocking subprocess or file system calls seems like extra complexity.

What to do instead

Structure code into small functions with explicit inputs and return values so they are testable, then write unittest or pytest cases covering both success and failure. Use tempfile and tmp_path for file operations, unittest.mock.patch for subprocess.run and network calls, and assert on raised exceptions with assertRaises. Run the suite before every commit.

Mistake 10

Debugging by guessing: adding print statements randomly, catching exceptions with a bare except and continuing, or ignoring the traceback's last line and file/line reference.

Under time pressure candidates patch symptoms instead of reading the error; bare except: pass hides the real failure and turns a clear exception into wrong output later. They also skip logging and cannot reproduce the failure after the run ends.

What to do instead

Read the traceback bottom-up to the first frame in your own code, reproduce the failure in a minimal script or test, and use logging with levels instead of prints so the evidence survives. Catch specific exception types, log with exc_info=True, and re-raise when the caller cannot recover. Use pdb or breakpoint() to inspect state at the failing line rather than editing blindly.