HEAD Start
0/0
A field guide for new software engineers

How software actually gets built, one small commit at a time

Nobody is born knowing Git, code review or how a request reaches a database. Everyone on your team once typed git push and hoped for the best. This guide walks through the everyday parts of the job so they stop feeling mysterious.

No grades Every quiz answer explains itself Try things, nothing breaks 27 short chapters in 11 parts Starts from zero

Covers the tools many enterprise teams use: Java SQL MongoDB Python AWS CloudWatch Bitbucket Jira ConfluenceUsing GitHub, GitLab or another cloud instead? The ideas carry straight over.

What's inside: 11 short parts, 20–60 minutes each. Go at your own pace.

Part

Start here

Chapter

What software engineers actually do

Software is a set of instructions that tells computers what to do. Engineers write those instructions as code. But typing new code is a small part of the job. Most of it is understanding problems, reading existing code, working with people, and changing things safely.

You'll spend far more time reading code than writing it. That's normal, and it's how everyone learns a codebase.

Who's on a software team

RoleWhat they doYou'll work with them when…
Backend engineerBuilds services, APIs and databases (often in Java)You change how data is stored or processed
Frontend / mobile engineerBuilds the screens people use: web pages, the technician appYou change an API the app calls
QA engineerTests features and finds bugs before customers doYour ticket moves to testing
DevOps / platformRuns AWS, pipelines, monitoring and deploymentsYou need access, or a deploy fails
Product manager / ownerDecides what to build and why; writes and prioritises ticketsA ticket is unclear
DesignerDesigns how screens look and behaveYou build or change a screen
Tech lead / architectSets technical direction and reviews big decisionsYou're unsure how to approach something
SupportTalks to customers and reports their problemsA bug comes in from a customer

How work flows through the team

  1. A customer has a need or a problem.
  2. Product turns it into a ticket and decides when it gets done.
  3. Engineers design and build it, and review each other's code.
  4. QA tests it. It's released to customers.
  5. Customers use it, give feedback, and the cycle repeats.

A typical day

WhenWhat
MorningCheck messages and PR comments. Daily standup (15 minutes).
Late morningFocus time: read code, write code, run tests.
After lunchReview a teammate's PR. Maybe pair with someone on a tricky bug.
AfternoonA meeting or two: refinement, design discussion, 1:1 with your lead.
End of dayPush your work, update your ticket, note where to pick up tomorrow.

What's expected of you in your first months

Nobody expects you to know everything. They expect you to learn steadily, ask questions, be honest about progress, and ship small, reliable changes. That's it. Speed comes later, by itself.

Main takeaways
  1. Engineering is mostly understanding problems and reading code, not just typing it.
  2. Know who does what on your team, so you know who to ask.
  3. Early on, aim to learn, ask and ship small things reliably.
Chapter

Terminal basics

The terminal (also called the command line or shell) is a window where you control the computer by typing commands instead of clicking. Git, Maven, Python scripts and the AWS CLI all run here, so it's worth getting comfortable early.

  • Mac: the Terminal app (or iTerm2). It runs a shell called zsh.
  • Windows: use Git Bash or WSL so commands match this guide. PowerShell uses different commands.
  • IntelliJ and VS Code also have a built-in terminal at the bottom of the window.

Folders and paths

Your computer is a tree of folders (also called directories). The terminal is always "standing" in one folder, the current directory. A path describes where something is:

  • ~ is your home folder, e.g. /Users/you.
  • . is the current folder, and .. is the folder above it.
  • /Users/you/workorder-service is an absolute path (from the very top); workorder-service/src is a relative path (from where you are).
Try it · a practice terminal (nothing here touches your real computer)

    Type a command and press Enter. Type help to see what works. Use the Up arrow to repeat a command and Tab to finish a name.

    Commands to know

    CommandWhat it does
    pwdPrint where you are
    ls / ls -laList files / including hidden ones, with details
    cd folderGo into a folder. cd .. goes up, cd ~ goes home
    mkdir nameMake a new folder
    touch file.txtCreate an empty file
    cat file / less fileShow a file / page through a long file (press q to quit)
    cp a b / mv a bCopy / move or rename
    rm fileDelete a file. There is no trash bin: it's gone
    clearClear the screen
    historyShow commands you've typed

    Keyboard shortcuts that save time

    • Tab finishes file and folder names for you. Press it constantly.
    • ↑ brings back the previous command.
    • Ctrl+C stops a running command (like a server or a stuck script).
    • Ctrl+R searches your command history.
    • Names with spaces need quotes: cd "SWE tutorials".
    Main takeaways
    1. pwd, ls and cd are how you find your way around.
    2. Tab completion and the Up arrow do half the typing for you.
    3. Read what the terminal prints. Errors usually say exactly what's wrong.
    Chapter

    Your tools and first-week checklist

    Your first week is mostly about getting access, installing tools, and getting the project running on your laptop. It's slow for everyone, and that's fine. Here are the tools you'll likely use, and a checklist to track your progress.

    ToolWhat it's for
    IntelliJ IDEAThe main editor (IDE) for Java: code, run, debug, test
    VS CodeA lighter editor, handy for Python scripts and config files
    JDK and MavenJava itself, and the tool that builds Java projects
    GitVersion control (see Git in plain words)
    Python 3For team scripts
    DockerRuns databases and services locally in containers
    PostmanSends API requests while you develop
    AWS CLITalks to AWS from the terminal, e.g. to read logs
    MongoDB Compass / a SQL clientBrowse databases visually (e.g. DBeaver or DataGrip for SQL)
    Jira, Confluence, BitbucketTickets, documentation and code (Atlassian)
    Slack or TeamsTeam chat
    Try it · tick things off as you go (saved in this browser)

    Your team's exact list may differ. Ask your lead or onboarding buddy for theirs.

    Main takeaways
    1. Install the exact versions the README asks for.
    2. Your first real win is building and running the project locally.
    3. Write down every setup problem you hit, and fix the docs for the next person.
    Part

    How teams work

    Chapter 1

    How a piece of work travels from idea to users

    Every feature or bug fix you touch follows roughly the same path. Teams use different tools, but the shape stays the same. Once you know the path, you always know what comes next and who to ask.

    This path is often called the software development lifecycle (SDLC). You'll hear the word in interviews and planning meetings. It just means "the steps work goes through."

    Try it · click each step

    What "done" means

    Many teams agree on a Definition of Done: a checklist every ticket must pass before anyone calls it finished. A typical one:

    • Code is reviewed and approved by at least one teammate.
    • Automated tests are written and passing.
    • Documentation (README, doc comments, API docs) is updated if behaviour changed.
    • The change is merged and deployed to at least a test environment.
    • The ticket's acceptance criteria are all met.

    If your team has one, find it in the wiki and keep it open while you work.

    Main takeaways
    1. Work starts from a ticket. Understand the "why" before writing code.
    2. Your code reaches users only after review, automated checks and a merge.
    3. Small changes move through this path faster and with less stress.
    Chapter 2

    Agile, Scrum and the team's rhythm

    Agile is an approach where teams build software in small pieces, show it often, and adjust based on feedback. It replaced the old style of planning everything for a year and delivering it all at the end.

    Scrum is the most common way to do Agile. Work happens in sprints, fixed cycles usually two weeks long. Each sprint has the same set of meetings, called ceremonies.

    Try it · walk through one sprint

    Anatomy of a good ticket

    Tickets are often written as user stories: "As a type of user, I want something, so that a benefit." Below the story are acceptance criteria, the testable conditions that prove the work is done.

    PROJ-142  Add "Remember me" to login
    
    Story
    As a returning customer, I want to stay signed in on my own
    laptop, so that I don't type my password every day.
    
    Acceptance criteria
    ✓ A "Remember me" checkbox appears under the password field
    ✓ When checked, the session lasts 30 days instead of 1 day
    ✓ Signing out ends the session even if "Remember me" was checked
    
    Estimate  3 points        Labels  frontend, auth

    Story points and estimates

    Teams estimate effort in story points (often 1, 2, 3, 5, 8, 13) instead of hours. Points describe relative size and uncertainty: a 5 is "bigger and less clear than a 2". Your early estimates will be off. Everyone's are. Estimate honestly and say what you're unsure about.

    Your standup update

    The daily standup is 15 minutes, not a status report to a boss. Say what you did, what you'll do, and anything blocking you. Mentioning a blocker early is the most useful thing you can say.

    Vague
    Yesterday I worked on stuff.
    Today I'll continue.
    No blockers.
    Useful
    Yesterday: login checkbox UI done, PR open.
    Today: session length change on the API.
    Blocker: I don't have access to the
    staging database. Who can grant it?
    Main takeaways
    1. Agile means small pieces, shown often, adjusted by feedback.
    2. Acceptance criteria tell you exactly when a ticket is done. Read them first.
    3. Raise blockers at standup immediately. Waiting silently costs the whole team.
    Chapter

    Jira and Confluence: where work and knowledge live

    Atlassian makes three tools that many teams use every day, and they're linked together:

    • Jira holds the work: tickets, sprints and boards.
    • Confluence holds the knowledge: setup guides, architecture, runbooks, meeting notes.
    • Bitbucket holds the code: repositories, pull requests and pipelines (see the Bitbucket chapter).

    The glue is the issue key, like FSM-1234. Put it in your branch name, commit messages and pull request title, and Jira automatically shows the branch, commits and PR on the ticket's Development panel. Your manager can see progress without asking you.

    Issue types

    TypeWhat it isExample
    EpicA big goal made of many tickets, often weeks of workOffline mode for the technician app
    StoryA feature from the user's point of viewTechnician can attach photos to a work order
    TaskTechnical work that isn't a user featureUpgrade Spring Boot to 3.3
    BugSomething that should work and doesn'tWork order list crashes when an asset has no address
    Sub-taskA smaller piece of a story or taskAdd photo upload endpoint
    Try it · move a ticket across the board

    Your board's column names may differ slightly. Ask which statuses your team uses.

    Finding tickets with JQL

    JQL (Jira Query Language) is search for Jira. Paste these into Filters → Advanced issue search, then save the useful ones.

    -- Everything assigned to me that isn't finished
    assignee = currentUser() AND resolution = Unresolved ORDER BY priority DESC
    
    -- My tickets in the current sprint
    sprint in openSprints() AND assignee = currentUser()
    
    -- Open bugs in a project
    project = FSM AND type = Bug AND status != Done
    
    -- Anything mentioning the scheduler, changed in the last week
    project = FSM AND text ~ "scheduler" AND updated >= -7d

    Smart commits

    If your Jira admin has enabled them, commit messages can comment on a ticket or log time:

    git commit -m "FSM-1234 Handle assets with no address #comment Fixed null city in routing #time 2h"

    Using Confluence well

    • Search before asking. Look for pages like "Local setup", "Onboarding", "Architecture", "Runbook" and "Release notes".
    • Watch the pages you depend on (the eye icon) so you're notified when they change.
    • Fix what's out of date. If a setup step was wrong, edit the page. New joiners are the best at spotting stale docs.
    • Link both ways. Paste a Jira link into a Confluence page and it shows the live ticket status; link the page back from the ticket.
    • Write things down for the next person. Solved something tricky? A short how-to page saves the next joiner a day.
    Main takeaways
    1. Jira is for work, Confluence for knowledge, Bitbucket for code.
    2. Put the issue key (like FSM-1234) in branches, commits and PR titles so everything links up.
    3. Keep your ticket status accurate, and search Confluence before asking.
    Part

    Git basics

    Chapter 3

    Git in plain words

    Git is a version control system. It remembers every saved version of a project and lets many people change the same code without overwriting each other's work. Bitbucket (used in this guide's examples), GitHub and GitLab are websites that host a shared copy of that history and add pull requests, reviews and automation on top. The Git commands are identical on all of them.

    Three words, explained simply

    • Repository (repo): a project folder that Git is watching. It holds your files plus a hidden .git folder where every past version is kept. When people say "clone the repo", they mean "download the project and its history".
    • Commit: a snapshot of your changes with a short message ("Fix crash when cart is empty"), your name and the time. Each commit gets a unique ID like a1b2c3d, so anyone can point to it.
    • Remote: the copy of the repo on Bitbucket. Your laptop and Bitbucket each have a full copy; push and pull keep them in sync.

    Why not just save files normally? Because a normal save overwrites the old version. With Git you can always answer "what changed, who changed it, when, and why?", and you can go back if the new version is broken.

    First-time setup

    You do this once per computer, then once per project.

    # Tell Git who you are (shows on every commit)
    git config --global user.name "Asha Rao"
    git config --global user.email "asha@company.com"
    
    # Download a project and its whole history
    git clone git@bitbucket.org:your-team/workorder-service.git
    cd workorder-service

    The four places a change lives

    Most beginner confusion comes from not knowing which place a change is in.

    Try it · move a change all the way to Bitbucket

    Working folder

    Files on your laptop as you edit them.

    Staging area

    Changes you picked for the next save point.

    Local history

    Save points (commits) on your laptop only.

    Remote (Bitbucket)

    The shared copy your team can see.

    Start by editing a file.

    What never goes into Git: .gitignore

    A file called .gitignore lists things Git should pretend don't exist. Check it before your first commit in any project.

    # .gitignore
    node_modules/      # installed libraries, re-downloadable
    build/             # generated output
    .env               # passwords and API keys: never commit these
    *.log
    .DS_Store          # Mac clutter
    .idea/  .vscode/   # personal editor settings

    Reading history

    • git log --oneline lists recent commits, one per line.
    • git diff shows changes you haven't staged. git diff --staged shows what you're about to commit.
    • git show a1b2c3d shows one commit in full.
    • git blame file.js shows who last changed each line and in which commit. Use it to find context, not culprits.
    Main takeaways
    1. git add picks changes. git commit saves them locally. git push shares them.
    2. A commit on your laptop is invisible to the team until you push.
    3. When unsure, run git status. It's always safe and tells you where everything is.
    Chapter 4

    Branches, and how a team repo uses them

    main is the branch everyone trusts. It should always build and pass its tests. Nobody writes new work directly on it. You create your own branch, make your commits there, and bring them back into main through a pull request.

    Most repos protect main with branch protection rules: it can't be pushed to directly, and a pull request needs an approval and passing checks before it can merge. If a push to main is rejected, that's the rule working, not you breaking something.

    Merging just means taking the commits from one branch and adding them to another. When your feature branch is merged into main, your work becomes part of the official code that everyone builds on and that eventually ships to customers.

    Try it · complete the missions

      You are on main

      Common branch names

      Most teams follow a naming pattern so anyone can tell what a branch is for. Check your repo's CONTRIBUTING.md or ask your lead for the exact rules.

      BranchWhat it's forWho creates it
      mainAlways working code. Protected, so changes arrive only through a reviewed PR.Exists already
      developSome teams (using "Git Flow") collect finished features here before releasing to main.Exists already, if used
      feature/PROJ-142-remember-meNew behaviour. Lives for days, not months.You
      bugfix/PROJ-151-date-formatA fix for something broken that isn't urgent.You
      hotfix/payment-timeoutAn urgent fix for something broken in production.Usually senior engineers
      release/2.4A frozen version for final testing before shipping.Release owner

      Two common strategies optional

      • Trunk-based / GitHub Flow. Short feature branches off main, merged back within a day or a few days. Simple, and the most common today.
      • Git Flow. Adds develop, release/* and hotfix/* branches. Used by teams that ship versioned releases on a schedule, such as mobile apps.

      The daily routine

      # 1. Start from the latest main
      git switch main
      git pull
      
      # 2. Make your own branch
      git switch -c feature/PROJ-142-remember-me
      
      # 3. Work in small steps
      git add src/LoginForm.tsx
      git commit -m "Add remember-me checkbox to login form"
      
      # 4. Share it and open a pull request on Bitbucket
      git push -u origin feature/PROJ-142-remember-me
      Go deeper (optional): keeping your branch up to date

      Keeping your branch up to date

      While you work, teammates merge into main. Bring their changes into your branch every day or two, so surprises stay small. There are two ways:

      Merge (simplest, always safe)
      git switch main
      git pull
      git switch feature/PROJ-142-remember-me
      git merge main

      Adds a merge commit. History shows exactly what happened.

      Rebase (cleaner history)
      git fetch origin
      git rebase origin/main
      # branch already pushed? then:
      git push --force-with-lease

      Replays your commits on top of the latest main. Only rebase branches that you alone work on.

      Main takeaways
      1. Never commit straight to main. Always branch from a fresh main.
      2. Name branches so a stranger knows what they are: type, ticket, short description.
      3. Short-lived branches, updated often, cause fewer merge conflicts.
      Part

      Git with your team

      Chapter 5

      Merge conflicts are normal

      A merge conflict happens when two branches changed the same lines, and Git can't tell which version is right. Git stops and asks a human. Nothing is broken and nothing is lost. Every engineer resolves conflicts regularly.

      Git marks the conflicting part of the file with three markers:

      • <<<<<<< HEAD: start of the version on the branch you're on.
      • =======: the divider.
      • >>>>>>> other-branch: end of the incoming version.

      Your job: edit the file so it contains the correct final code, delete the three marker lines, then stage and commit.

      Try it · resolve a real-looking conflict

      You ran git merge main on your branch. Your teammate's branch added a logger import. Your branch added a retry import. Both are used further down.

      Resolving step by step

      1. Run git status. It lists files under "both modified".
      2. Open each file. Most editors (VS Code, IntelliJ) highlight conflicts and offer "Accept current / incoming / both" buttons.
      3. Decide what the final code should be. Often it's a mix of both sides.
      4. Remove all marker lines. Run the app and tests.
      5. git add each fixed file, then git commit (or git rebase --continue during a rebase).
      6. Unsure which side is right? Ask the person who wrote the other change. git log tells you who.

      Want to give up and start over? git merge --abort puts everything back exactly as it was before the merge.

      Main takeaways
      1. A conflict is Git asking a question, not an error you caused.
      2. The right answer is often a combination of both sides. Read both before choosing.
      3. git merge --abort is your safe exit. Pulling main often keeps conflicts small.
      Chapter 6

      "Oops" — undoing mistakes in Git

      Git almost never truly loses committed work. Most mistakes have a calm, two-command fix. Pick a situation below to see it.

      Try it · what happened?
      Two commands to avoid until you're confident: git reset --hard throws away uncommitted work permanently, and git push --force can overwrite teammates' commits on a shared branch. If you think you need either, ask someone first. There's no shame in it; seniors double-check these too.

      Revert vs. reset

      • git revert makes a new commit that undoes an old one. History stays intact. Safe on shared branches.
      • git reset moves your branch backwards, as if commits never happened. Fine for local commits nobody else has. Dangerous once pushed.
      Main takeaways
      1. Pushed something wrong? Use git revert, not reset.
      2. git reflog remembers where your branch has been. It can rescue "lost" commits.
      3. When in doubt, stop and ask before running any command with --hard or --force.
      Chapter 7

      Commit messages, pull requests and code review

      Six months from now, someone will read your commit to figure out why a line exists. That person might be you. A good message saves them an hour.

      A useful pattern: start with a verb, say what changes, keep the first line under about 70 characters. If the reason isn't obvious, add a blank line and a short paragraph explaining why.

      Hard to use later
      fix
      wip
      changes
      final final v2
      updated stuff
      Easy to use later
      Fix crash when cart is empty
      Add retry to payment API call
      Rename getData to fetchOrders
      Remove unused date helper

      Conventional Commits

      Many teams prefix messages with a type. Tools then generate changelogs and version numbers automatically.

      PrefixMeaningExample
      feat:New featurefeat: add remember-me option to login
      fix:Bug fixfix: handle empty cart in checkout total
      refactor:Code change with no behaviour changerefactor: extract price calculation
      test:Tests onlytest: cover expired discount codes
      docs:Documentation onlydocs: explain local setup in README
      chore:Tooling, dependencies, configchore: bump eslint to 9.2

      What a pull request (PR) is

      A pull request (GitLab calls it a merge request) asks the team: "please review my branch and merge it into main." It's where code review and automated checks happen. A good description answers three questions:

      ## What
      Adds a "Remember me" checkbox. When checked, sessions last 30 days.
      
      ## Why
      Returning customers asked to stay signed in. Ticket: PROJ-142
      
      ## How I tested
      - Unit tests for session length (new: session.test.ts)
      - Manually: signed in with and without the box, restarted browser
      - Screenshot of the form attached below
      
      ## Notes for reviewers
      I wasn't sure whether to store the flag in a cookie or the DB.
      Went with the cookie. Happy to change.

      Receiving review comments

      Review comments are about the code, not about you. Senior engineers get lots of comments too. Reply to each one: change the code, or explain your reasoning politely. "Good catch, fixed in a3f9c1" is a perfectly good reply.

      Reviewing other people's code

      You'll be asked to review soon, even as a junior. Your fresh eyes are valuable. Ask questions freely. A simple checklist:

      • Does it do what the ticket asks? Did I try it?
      • Are there tests? Would they catch a regression?
      • Are names clear? Could I understand this in six months?
      • What happens with empty, null or very large input?
      • Any passwords, keys or personal data in the diff?

      Label comments so the author knows how important they are: nit: (tiny, optional), question: (I want to understand), suggestion: (consider this), blocking: (must fix before merge). Always say what you liked, too.

      Main takeaways
      1. Commit messages start with a verb and say what changed.
      2. Keep PRs small. Under about 300 changed lines gets reviewed faster and better.
      3. Treat review comments as free mentoring, and review others kindly and specifically.
      Chapter

      Bitbucket, day to day

      Bitbucket Cloud is where many teams keep their repositories, pull requests and pipelines. It's tightly linked with Jira, so if your team uses both, most of your daily flow starts from a ticket and ends with a merged PR that updates that ticket.

      Try it · one ticket's journey through Bitbucket

      Merge strategies

      When you click Merge, Bitbucket asks how. Your team probably has a default; use it.

      StrategyWhat ends up on mainGood for
      Merge commitAll your commits, plus one merge commitKeeping the full story of how the work happened
      SquashAll your commits combined into oneA tidy main branch with one commit per ticket
      Fast-forwardYour commits added in a straight line, no merge commitLinear history. Only works if your branch is up to date with main

      Signing in from the command line

      • SSH (recommended): generate a key once, add the public half to Bitbucket under Personal settings → SSH keys, then clone with the git@bitbucket.org:… URL. No passwords after that.
      • HTTPS: use an API token from your Atlassian account settings, never your Atlassian password. Older guides mention "app passwords"; Atlassian has replaced them with API tokens.
      • Never share your private key (id_ed25519, the file without .pub) with anyone, including in tickets or chat.

      Bitbucket Pipelines

      Your repo's CI lives in a file called bitbucket-pipelines.yml at the root. It decides which builds and tests run on each PR and branch. The shipping chapter walks through an example. When a pipeline fails, click into the failed step and read the log from the first red line.

      Main takeaways
      1. Create branches from the Jira ticket so the key is in the name and everything links.
      2. A PR merges only with approvals, resolved tasks and a green pipeline.
      3. Use SSH keys or API tokens. Your private key and password never leave your laptop.
      Part

      Java and Python

      Chapter

      Java essentials for backend work

      Most enterprise backend services are written in Java, usually with Spring Boot and built with Maven or Gradle. You don't need to know all of Java on day one. You need to find your way around a service and make a safe change.

      What is Spring Boot?

      A framework is ready-made code that handles the boring, repetitive parts of an app, so you only write the parts specific to your business. Spring Boot starts a web server, turns incoming HTTP requests into Java method calls, turns Java objects into JSON responses, and connects to databases. You write the logic: "when a work order is created, check the asset exists and assign a technician".

      Two Spring ideas confuse almost every beginner, so here they are in plain words:

      • Annotations are the @Something labels above classes and methods. They're instructions to Spring. @GetMapping("/{id}") means "call this method when a GET request arrives at this path".
      • Dependency injection means you don't create the objects you need yourself. You list them in your constructor, and Spring hands them to you. That makes it easy to pass in a fake version during tests.

      How a typical service is laid out

      workorder-service/
      ├── pom.xml                                # dependencies and build settings (Maven)
      ├── src/main/java/com/example/workorder/
      │   ├── WorkOrderApplication.java          # starts the app
      │   ├── api/WorkOrderController.java       # HTTP endpoints
      │   ├── service/WorkOrderService.java      # business rules
      │   ├── repository/WorkOrderRepository.java # database access
      │   └── model/WorkOrder.java               # data classes
      ├── src/main/resources/application.yml     # configuration
      └── src/test/java/com/example/workorder/   # tests mirror the main folders

      The three layers

      A request flows down through the layers and the answer flows back up. Each layer has one job, so you always know where a change belongs.

      LayerJobShould not…
      ControllerReceives HTTP requests, validates input, returns responses and status codesContain business rules or SQL
      ServiceBusiness rules: "a closed work order can't be reassigned"Know about HTTP details
      RepositoryReads and writes the database (SQL or MongoDB)Make business decisions
      @RestController
      @RequestMapping("/api/work-orders")
      public class WorkOrderController {
      
          private final WorkOrderService service;
      
          // Spring passes the service in for us ("dependency injection")
          public WorkOrderController(WorkOrderService service) {
              this.service = service;
          }
      
          @GetMapping("/{id}")
          public WorkOrder get(@PathVariable long id) {
              return service.findById(id);
          }
      
          @PostMapping
          @ResponseStatus(HttpStatus.CREATED)
          public WorkOrder create(@Valid @RequestBody NewWorkOrder request) {
              return service.create(request);
          }
      }
      
      // In WorkOrderService: Optional avoids returning null
      public WorkOrder findById(long id) {
          return repository.findById(id)
              .orElseThrow(() -> new NotFoundException("Work order " + id + " not found"));
      }

      Core Java ideas you'll see daily

      ConceptIn plain words
      Class and objectA class is a blueprint (WorkOrder); an object is one real instance (work order 88213).
      InterfaceA promise of which methods exist, without the code. Lets you swap implementations, e.g. a fake repository in tests.
      CollectionsList (ordered), Map (key → value), Set (no duplicates). See the Big-O chapter for choosing.
      GenericsList<WorkOrder> means "a list that only holds work orders". The compiler checks it for you.
      ExceptionsHow Java reports errors. Checked ones must be handled or declared; unchecked ones (like NullPointerException) usually mean a bug.
      OptionalA box that may be empty. Forces you to handle "not found" instead of returning null.
      Streamsorders.stream().filter(o -> o.isOpen()).toList(): transform collections in a readable pipeline.
      AnnotationsThe @Something labels. They tell Spring what a class or method is for.

      The classic Java gotcha: comparing strings

      Sometimes works, sometimes doesn't
      // == compares memory addresses
      if (status == "OPEN") { ... }
      Always correct, null-safe
      // equals compares the text
      if ("OPEN".equals(status)) { ... }
      // better still: an enum
      if (status == Status.OPEN) { ... }

      Handling exceptions

      An empty catch block is the most common Java mistake in code reviews. It hides the error, so the bug shows up later somewhere confusing.

      Swallows the error
      try {
          routing.assign(order);
      } catch (Exception e) {
          // nothing here: the failure vanishes
      }
      Specific, logged, reported
      try {
          routing.assign(order);
      } catch (RoutingException e) {
          log.warn("Could not route work order {}",
                   order.getId(), e);
          throw new ConflictException(
              "No technician available", e);
      }

      Build commands

      I want to…MavenGradle
      Build and run all testsmvn clean install./gradlew build
      Run tests onlymvn test./gradlew test
      Run one test classmvn test -Dtest=WorkOrderServiceTest./gradlew test --tests WorkOrderServiceTest
      Start a Spring Boot appmvn spring-boot:run./gradlew bootRun
      See where a library comes frommvn dependency:tree./gradlew dependencies
      Main takeaways
      1. Controller handles HTTP, service holds business rules, repository talks to the database.
      2. Compare strings with .equals(), and prefer Optional over returning null.
      3. mvn test before every push. Learn to run one test class on its own.
      Chapter

      Running Python scripts safely

      Teams keep Python scripts for one-off and repeated jobs: fixing bad data, exporting reports, calling AWS, running migrations. You'll often be asked to run a script someone else wrote. Doing that safely is a skill.

      Try it · from fresh checkout to a safe run

      What a well-behaved script looks like

      Read a script before you run it. Good ones explain themselves, require you to choose an environment, and support a dry run.

      #!/usr/bin/env python3
      """Close work orders that have been COMPLETED for 30+ days.
      
      Usage:
          python close_stale_orders.py --env dev --dry-run
      """
      import argparse, os, sys
      from datetime import datetime, timedelta, timezone
      from pymongo import MongoClient
      
      
      def main() -> int:
          parser = argparse.ArgumentParser(description=__doc__)
          parser.add_argument("--env", choices=["dev", "staging", "prod"], required=True)
          parser.add_argument("--dry-run", action="store_true", help="show changes, change nothing")
          args = parser.parse_args()
      
          db = MongoClient(os.environ["MONGO_URI"])["fieldservice"]
          cutoff = datetime.now(timezone.utc) - timedelta(days=30)
          query = {"status": "COMPLETED", "completedAt": {"$lt": cutoff}}
      
          count = db.workOrders.count_documents(query)
          if args.dry_run:
              print(f"[dry run] would close {count} work orders in {args.env}")
              return 0
      
          result = db.workOrders.update_many(query, {"$set": {"status": "CLOSED"}})
          print(f"Closed {result.modified_count} work orders in {args.env}")
          return 0
      
      
      if __name__ == "__main__":
          sys.exit(main())

      Errors you'll hit, and what they mean

      ErrorUsual causeFix
      ModuleNotFoundError: No module named 'pymongo'Virtual environment not active, or packages not installedsource .venv/bin/activate then pip install -r requirements.txt
      python: command not foundYour system only has python3Use python3, or activate the venv (it provides python)
      KeyError: 'MONGO_URI'A required environment variable isn't setexport MONGO_URI=… or fill in the .env file the README describes
      NoCredentialsError / ExpiredTokenAWS login missing or expiredaws sso login --profile dev
      Permission deniedThe file isn't marked executableRun it as python3 script.py instead
      SyntaxError on f-strings or type hintsPython version too oldCheck python3 --version against the README
      Production rule: never run a script that changes data in production without (1) reading it, (2) a dry run, (3) running it on dev or staging first, and (4) approval from your lead. Post the dry-run output in the ticket.
      Main takeaways
      1. One virtual environment per project. Activate it before installing or running anything.
      2. Read the script and its --help before running it.
      3. Dry run first, dev before prod, and get approval for production changes.
      Part

      Databases

      Chapter

      SQL and joins

      A relational database stores data in tables. Each row is one record, each column one field. Every table has a primary key (a unique ID). A foreign key is a column that points to another table's primary key. That's how tables relate.

      For example, a work_orders table might have a technician_id column. The value 58 in that column means "this work order belongs to the technician whose id is 58 in the technicians table". Instead of copying the technician's name into every work order, you store their ID once and look it up when needed.

      You'll also see NULL, which means "no value at all", not zero and not an empty string. A work order with technician_id = NULL simply hasn't been assigned yet. To find those, write WHERE technician_id IS NULL; = NULL never matches anything.

      The statements you'll use 90% of the time

      SELECT id, title, status            -- which columns
      FROM work_orders                     -- which table
      WHERE status = 'OPEN'                -- which rows
      ORDER BY created_at DESC             -- newest first
      LIMIT 20;
      
      -- How many open orders does each technician have?
      SELECT technician_id, COUNT(*) AS open_orders
      FROM work_orders
      WHERE status = 'OPEN'
      GROUP BY technician_id
      HAVING COUNT(*) > 5;                 -- HAVING filters groups, WHERE filters rows
      
      INSERT INTO work_orders (title, status) VALUES ('Inspect pump', 'OPEN');
      UPDATE work_orders SET status = 'CLOSED' WHERE id = 104;
      DELETE FROM work_orders WHERE id = 104;

      Joins: combining tables

      A JOIN matches rows from two tables using a shared value, usually a foreign key. The join type decides what happens to rows that have no match.

      Try it · switch the join type and watch the result
      technicians
      idname
      work_orders
      idtitletechnician_id
      
              

      result
      nametitle
      Before any UPDATE or DELETE: run a SELECT with the exact same WHERE first and check the row count. An UPDATE without WHERE changes every row in the table. On shared databases, wrap changes in a transaction: BEGIN; … check … COMMIT; (or ROLLBACK; to undo).

      Indexes, briefly

      An index is like the index at the back of a book: it lets the database find rows without reading the whole table. Columns you filter or join on often (technician_id, status) usually need one. If a query is slow, run EXPLAIN before it to see whether an index is used.

      Main takeaways
      1. INNER JOIN keeps only matches; LEFT JOIN keeps every row from the left table and fills gaps with NULL.
      2. WHERE filters rows, GROUP BY groups them, HAVING filters groups.
      3. Always SELECT before you UPDATE or DELETE, and never run them without a WHERE.
      Chapter

      MongoDB: documents instead of tables

      MongoDB stores documents: JSON-like objects that can contain nested objects and arrays. Documents live in collections. Two documents in the same collection don't have to have exactly the same fields, which makes MongoDB good for data whose shape varies, such as forms that differ per customer.

      What is JSON?

      JSON is a simple text format for data that both people and programs can read. You'll see it everywhere: in MongoDB, in API requests and responses, and in config files. It has only a few building blocks:

      • { } an object: a set of "name": value pairs, like one work order.
      • [ ] an array: an ordered list, like a list of notes.
      • Values are text in quotes ("OPEN"), numbers (2), true/false, null, or another object or array.

      A MongoDB document is basically a JSON object, with a few extra types such as dates and IDs.

      // One document in the workOrders collection
      {
        "_id": ObjectId("66f1c2a9e4b0a1d2c3e4f5a6"),
        "number": "WO-88213",
        "status": "OPEN",
        "priority": 2,
        "technicianId": 58,
        "asset": { "id": "MTR-4471", "type": "meter", "city": "Austin" },   // nested object
        "notes": [                                                    // array
          { "by": "dispatcher", "text": "Gate code 4410" }
        ],
        "createdAt": ISODate("2026-09-30T09:14:02Z")
      }
      SQL wordMongoDB word
      DatabaseDatabase
      TableCollection
      RowDocument
      ColumnField
      Primary key_id (added automatically)
      JOIN$lookup, or embed the related data inside the document
      Try it · the same question in MongoDB and SQL

      Run these in mongosh (the MongoDB shell) or in the MongoDB Compass app.

      SQL or MongoDB?

      • SQL fits data with clear relationships and strict consistency: billing, inventory, users and permissions.
      • MongoDB fits flexible or nested data read as a whole: a work order with its notes, photos and custom form answers.
      • Many systems use both. Ask which data lives where in your services.
      Careful: db.workOrders.updateMany({}, …) and deleteMany({}) with an empty filter touch every document. Run find() or countDocuments() with the same filter first.
      Main takeaways
      1. MongoDB stores flexible JSON-like documents in collections.
      2. find(filter, projection) reads; updateOne/updateMany with $set change data; aggregate groups and joins.
      3. Test any filter with find or countDocuments before updating or deleting.
      Part

      Writing clear code

      Chapter 8

      Method-level comments and documentation

      A method-level comment (also called a doc comment or docstring) sits right above or inside a function. It tells other developers how to use the function without reading its code. A complete one covers:

      • Summary: one sentence on what the function does.
      • Parameters: what each input means, its units, and whether it can be empty or null.
      • Return value: what comes back and any guarantees ("never negative").
      • Errors: which exceptions it throws and when.
      • Side effects (if any): sends an email, writes to the database, changes a global.

      Editors read these comments. When a teammate hovers over your function in their IDE, your comment pops up. Tools like Javadoc, Sphinx, JSDoc and DocFX also turn them into documentation websites.

      
              

      Explain why, not what

      The code already shows what happens. Comments are most useful when they explain intent, assumptions or surprises the code can't express.

      Repeats the code
      // add 1 to page
      page = page + 1;
      
      // loop over users
      for (User u : users) {
      
      // TODO fix this
      Explains intent
      // The orders API counts pages from 1
      page = page + 1;
      
      // Skip suspended users: billing has
      // already closed their accounts
      for (User u : activeUsers) {
      
      // TODO(PROJ-201): remove once v1 API is retired

      Other kinds of documentation

      KindWhere it livesWhat it answers
      Doc commentAbove a function or classHow do I use this function?
      Inline commentNext to a tricky lineWhy is this line written this odd way?
      READMERoot of the repoWhat is this project and how do I run it locally?
      API docs (OpenAPI/Swagger)Generated from code or a spec fileWhich endpoints exist and what do they accept?
      ADR (Architecture Decision Record)docs/adr/ folderWhy did we choose this database or design?
      RunbookTeam wikiThe service is down at 2am. What do I do?
      Main takeaways
      1. Every public method deserves a doc comment: purpose, parameters, return value, errors.
      2. Comments explain why. Clear names explain what.
      3. When you change the code, update the comment in the same commit. A wrong comment is worse than none.
      Chapter 9

      Clean code principles

      Code is read far more often than it's written. "Clean code" just means code the next person can understand and change safely. These principles have names you'll hear in reviews.

      PrincipleIn plain words
      Meaningful namesdaysUntilExpiry, not d. Functions are verbs (sendInvoice), booleans read as questions (isActive, hasAccess).
      DRYDon't Repeat Yourself. If the same logic is copy-pasted in three places, a bug fix will be missed in one. Extract it.
      KISSKeep It Simple. The boring solution your teammates understand beats the clever one they don't.
      YAGNIYou Aren't Gonna Need It. Build what the ticket needs now, not what you imagine might be needed one day.
      Single responsibilityOne function, one job. If you need "and" to describe it, split it.
      No magic numbersif (age >= ADULT_AGE), not if (age >= 18) scattered everywhere.
      Early returnHandle bad cases first and exit, so the main logic isn't buried five levels deep.
      Boy Scout ruleLeave code a little cleaner than you found it. A small rename is fine; a giant unrelated refactor belongs in its own PR.

      A small refactor, before and after

      Same behaviour, much easier to read and change.

      Before
      function calc(u) {
        if (u != null) {
          if (u.age >= 18) {
            if (u.c == "IN" || u.c == "US") {
              return u.p * 0.9;
            }
          }
          return u.p;
        }
      }
      After
      const ADULT_AGE = 18;
      const DISCOUNT_COUNTRIES = ["IN", "US"];
      const ADULT_DISCOUNT = 0.9;
      
      function priceFor(user) {
        if (!user) throw new Error("user is required");
        const eligible = user.age >= ADULT_AGE
          && DISCOUNT_COUNTRIES.includes(user.country);
        return eligible ? user.price * ADULT_DISCOUNT : user.price;
      }

      What changed: a name that says what the function does, constants instead of magic numbers, full property names, an early exit for missing input (the old version silently returned undefined), and no deep nesting.

      Go deeper (optional): SOLID and code smells

      SOLID, in one line each

      You'll hear "SOLID" in object-oriented codebases. Don't memorise it yet; just recognise it.

      • Single responsibility: a class has one reason to change.
      • Open/closed: add behaviour by adding code, not by editing working code.
      • Liskov substitution: a subclass should work anywhere its parent works.
      • Interface segregation: many small interfaces beat one giant one.
      • Dependency inversion: depend on interfaces, not concrete classes, so parts are swappable and testable.

      Code smells to notice

      Functions longer than a screen. More than three or four parameters. Deep nesting. Copy-pasted blocks. Comments explaining confusing code that could be renamed instead. Boolean parameters like save(true, false). None are bugs, but each is a hint that code could be clearer.

      Main takeaways
      1. Write for the reader. Good names do most of the work.
      2. Simple and obvious beats clever. Build only what's needed now.
      3. Refactor in small, separate steps, with tests protecting you.
      Part

      Testing, debugging and speed

      Chapter 10

      Testing: proving your code works

      An automated test is code that runs your code and checks the result. Tests let you change things confidently: if you break something, a test turns red within seconds instead of a customer finding it next week.

      In plain words, a test says: "given this situation, when I call this code, then I expect this result." For example: given an adult customer in India with a price of 100, when I calculate the price, then I expect 90. If someone later breaks the discount logic, that test fails immediately and tells them exactly what broke.

      Teams usually think about tests as a pyramid: many small fast tests at the bottom, a few slow realistic ones at the top.

      Try it · climb the test pyramid

      Anatomy of a unit test: Arrange, Act, Assert

      These tests cover the priceFor function from the clean code chapter. Notice one test per behaviour, including the error case.

      // priceFor.test.js  (Jest)
      describe("priceFor", () => {
        test("gives adults in discount countries 10% off", () => {
          // Arrange: set up the input
          const user = { age: 30, country: "IN", price: 100 };
          // Act: call the code
          const result = priceFor(user);
          // Assert: check the outcome
          expect(result).toBe(90);
        });
      
        test("charges minors full price", () => {
          expect(priceFor({ age: 16, country: "IN", price: 100 })).toBe(100);
        });
      
        test("throws when user is missing", () => {
          expect(() => priceFor(null)).toThrow("user is required");
        });
      });

      Words you'll hear

      TermMeaning
      AssertionThe check inside a test: expect(x).toBe(90).
      Edge caseUnusual input: empty list, zero, null, huge numbers, emoji in names, leap years.
      Mock / stubA fake version of something slow or external (payment API, email) so tests run fast and offline.
      RegressionSomething that used to work and broke. A test added with a bug fix prevents it coming back.
      CoverageThe percentage of lines your tests run. Useful hint, poor goal: 100% coverage can still miss bugs.
      Flaky testPasses sometimes, fails sometimes, with no code change. Usually timing or shared state. Report it; don't just re-run.
      TDDTest-Driven Development: write a failing test first, make it pass, then tidy up. "Red, green, refactor."
      Main takeaways
      1. Most tests should be small, fast unit tests. Add fewer, slower integration and end-to-end tests on top.
      2. Test behaviour and edge cases, not just the happy path.
      3. Every bug fix comes with a test that would have caught it.
      Chapter 11

      Debugging without panic

      Debugging is a skill with a method, not a talent. When something breaks, work through these steps in order:

      1. Reproduce it. Find exact steps that make the bug happen every time.
      2. Read the whole error message. Slowly. It usually tells you what and where.
      3. Narrow it down. Which file, function, line? Which input triggers it?
      4. Form a guess, then test it. "I think items is empty here." Add a log line or breakpoint to check.
      5. Fix the cause, not the symptom. Wrapping it in try/catch to hide the error is not a fix.
      6. Add a test that fails without your fix and passes with it.
      Try it · read a stack trace

      A request crashed. Here's the error from the logs. A stack trace lists the chain of method calls that led to the crash. Click the line where you'd start looking for the bug.

      Reading Java exceptions

      • Java prints the error message first, then the at … lines. The top at line is where the exception was thrown. (Python is the opposite: the error is at the bottom.)
      • Skip frames from org.springframework, java., jdk. and other libraries. Find the first frame in your own package.
      • Look for Caused by: further down. The last "Caused by" is usually the real root cause; the top exception is often just a wrapper.
      • Modern Java explains NullPointerExceptions: "because the return value of getCity() is null" tells you exactly what was missing.

      Debugger basics in IntelliJ IDEA

      Start the app with the bug icon (Debug) instead of Run, then click in the left margin next to a line number to add a breakpoint.

      ActionWhat it doesWindows / LinuxMac
      Step overRun this line, stop at the next oneF8F8
      Step intoGo inside the method called on this lineF7F7
      Step outFinish this method, return to the callerShift+F8Shift+F8
      ResumeRun until the next breakpointF9Cmd+Opt+R
      Evaluate expressionRun any code using the current variablesAlt+F8Opt+F8

      Right-click a breakpoint to make it conditional, e.g. stop only when id == 88213. That's how you catch one bad record among thousands.

      Tools that help

      • Debugger with breakpoints. Pause the program on a line and inspect every variable. Learn your IDE's debugger in week one; it's the biggest time saver.
      • Logging. console.log / print is fine locally. Remove it before committing, or use the project's real logger.
      • Rubber duck debugging. Explain the problem out loud, line by line, to anyone or anything. You'll often spot the bug mid-sentence.
      • git bisect. Something worked last week and doesn't now? Bisect binary-searches your commits to find the one that broke it.
      • Logs for deployed code. You can't attach a debugger to production. Use the request ID and CloudWatch (the AWS chapter).
      • Search the exact error message in quotes, along with the library name.
      Main takeaways
      1. Reproduce first. A bug you can trigger on demand is half-solved.
      2. Find the first stack frame in your team's code (top of a Java trace, bottom of a Python one), and read any "Caused by".
      3. Test one guess at a time, and finish with a regression test.
      Chapter 12

      Performance, Big-O and data structures

      Big-O notation describes how the work your code does grows as the input grows. It ignores exact timings and asks: if the list is ten times bigger, is the code ten times slower? A hundred times? Not slower at all?

      Here's a story that happens on real teams. A developer writes a screen that compares every work order with every other work order to find duplicates. With 100 work orders in the test database, that's 10,000 comparisons: instant. A big customer has 200,000 work orders. That's 40 billion comparisons, and the screen never loads. Nothing was "wrong" with the code; it just didn't scale. Big-O is the tool for spotting that in advance.

      Try it · grow the input
      1,000

      Bar length uses a log scale (each tick is 10× more work). Times assume about 100 million simple steps per second.

      Big-ONameEveryday example
      O(1)ConstantReading arr[5], looking up a key in a hash map
      O(log n)LogarithmicBinary search in a sorted list, database index lookup
      O(n)LinearOne loop over a list, list.includes(x)
      O(n log n)LinearithmicGood sorting algorithms (sort() in most languages)
      O(n²)QuadraticA loop inside a loop over the same list, e.g. comparing every pair

      Picking a data structure

      StructureGood atUse it when
      Array / listOrdered items, access by positionYou need order or iterate everything
      Hash map / dictFind a value by key in O(1)You look things up by ID or name
      Set"Have I seen this?" in O(1), no duplicatesRemoving duplicates, membership checks
      QueueFirst in, first outProcessing jobs in arrival order
      StackLast in, first outUndo history, walking nested structures
      TreeHierarchy, sorted dataFolders, org charts, DOM, database indexes
      O(n²): list lookup inside a loop
      for (const order of orders) {
        // scans every customer, every time
        const c = customers.find(
          x => x.id === order.customerId);
        print(c.name, order.total);
      }
      O(n): build a map once
      const byId = new Map(
        customers.map(c => [c.id, c]));
      
      for (const order of orders) {
        const c = byId.get(order.customerId);
        print(c.name, order.total);
      }
      The N+1 query problem. The same mistake with a database is the most common performance bug in real apps: one query to load 100 orders, then 100 more queries to load each customer. Load related data in one query (a JOIN, or your ORM's "include"/"eager load" option).

      Most importantly: write clear, correct code first. Optimise only when something is measurably slow, and measure again after.

      Main takeaways
      1. Big-O tells you how code slows down as data grows. Nested loops over big lists are the usual culprit.
      2. A hash map or set turns repeated searches into instant lookups.
      3. Correct and readable first. Measure before optimising.
      Part

      APIs and the cloud

      Chapter 13

      How apps, APIs and databases fit together

      Most software you'll work on follows the client–server pattern. A client (browser, mobile app) asks for something. A server (backend) does the work, usually reading or writing a database, and sends back an answer. They talk through an API: an agreed list of requests the server accepts.

      • API (Application Programming Interface) is a contract: "send me this, in this format, and I'll send you that back." The mobile app doesn't need to know how the server works inside; it only needs the contract.
      • An endpoint is one specific address in the API, like /api/work-orders. Each endpoint does one kind of job.
      • Requests and responses usually carry data as JSON (explained in the MongoDB chapter).
      • HTTP is the set of rules for how requests and responses are written and sent. HTTPS is the same, encrypted so nobody in between can read it.
      Try it · follow one click through the system

      What a request and response look like

      Every request has a method (what to do), a path (to what), headers (extra info such as who you are) and sometimes a body (the data, usually JSON). The response has a status code, headers and usually a body.

      # Request: technician app → server
      POST /api/work-orders HTTP/1.1
      Host: api.example.com
      Authorization: Bearer eyJhbGci...          # who is asking
      Content-Type: application/json             # the body is JSON
      
      { "title": "Replace meter", "assetId": "MTR-4471", "priority": 2 }
      
      # Response: server → app
      HTTP/1.1 201 Created
      Location: /api/work-orders/88213
      Content-Type: application/json
      
      { "id": 88213, "status": "OPEN", "title": "Replace meter", "assetId": "MTR-4471", "priority": 2 }

      GET, POST, PUT, PATCH, DELETE

      The method says what kind of action the request is. Together they cover CRUD: Create, Read, Update, Delete. Click each one to see a real request and response.

      Try it · one API, five methods
      MethodDoesChanges data?Same result if sent twice?
      GETReadNo (it's "safe")Yes
      POSTCreate something new, or trigger an actionYesNo: twice creates two work orders
      PUTReplace the whole resourceYesYes
      PATCHChange some fieldsYesUsually
      DELETERemoveYesYes (it's already gone)

      "Same result if sent twice" is called idempotent. It matters for field apps on bad mobile connections: if the app retries a request, retrying a PUT is harmless, but retrying a POST can create duplicates unless the API guards against it.

      Tools for calling APIs

      • Postman or Insomnia: save requests in collections and share them with the team. Ask if your team has a shared collection.
      • curl in the terminal: quick, and easy to paste into a ticket.
      • IntelliJ HTTP Client: write requests in a .http file next to the code.
      • Swagger UI / OpenAPI: many Spring Boot services publish interactive API docs, often at /swagger-ui.html.

      Status codes worth memorising

      The first digit tells you who to look at: 2xx success, 4xx the client sent something wrong, 5xx the server failed.

      CodeMeaningUsually means you should…
      200 OKSuccessCelebrate quietly
      201 CreatedSomething new was createdRead the new ID from the response
      400 Bad RequestInput was invalidCheck the request body and validation errors
      401 UnauthorizedNot signed in, or token expiredCheck the auth token
      403 ForbiddenSigned in, but not allowedCheck the user's permissions
      404 Not FoundThat URL or item doesn't existCheck the path and ID
      500 Internal Server ErrorThe server crashedRead the server logs for a stack trace
      503 Service UnavailableServer overloaded or downCheck health dashboards; retry later

      Databases in one paragraph each

      • Relational (SQL), see the SQL chapter: PostgreSQL, MySQL, SQL Server. Data in tables with rows and columns, linked by IDs. Strong rules keep data consistent. The default choice for most business apps.
      • NoSQL, see the MongoDB chapter: MongoDB (documents), Redis (key–value, often a cache), DynamoDB. Flexible shapes, scales easily for specific access patterns.
      • Migrations: versioned scripts that change the database structure (add a column, a table). They live in the repo and run in order, so every environment's database matches the code.
      Main takeaways
      1. Client asks, server works, database stores. The API is the contract between them.
      2. 4xx means look at the request; 5xx means look at the server logs.
      3. Database structure changes go through migrations in the repo, never by hand in production.
      Chapter

      AWS and reading logs in CloudWatch

      AWS (Amazon Web Services) rents out servers, databases, storage and hundreds of other services, so companies don't run their own data centres. If your services run there, then when something goes wrong in dev, staging or production, their logs in CloudWatch are usually the first place you look.

      AWS services you'll hear about

      Most teams use a subset of these. Ask which ones, and in which accounts and regions.

      • An account is a separate, walled-off area of AWS. Companies often have one account for dev, one for staging and one for production, so a mistake in dev can't touch production.
      • A region is a physical location with data centres, like us-east-1 (Virginia) or ap-south-1 (Mumbai). Your services, and their logs, live in a specific region.
      • A container is your app packaged together with everything it needs to run (the right Java version, settings, libraries), so it runs the same on any machine. Docker builds containers; ECS or EKS run many of them.
      ServiceWhat it is
      EC2Virtual servers you rent by the hour
      ECS / EKSRun containers (Docker) at scale; EKS is managed Kubernetes
      LambdaRun a function on demand without managing a server
      S3File storage ("buckets"): photos, exports, backups
      RDSManaged SQL databases (PostgreSQL, MySQL, Oracle…)
      SQS / SNSQueues and notifications that pass messages between services
      API Gateway / Load balancerThe front door that routes requests to services
      CloudWatchLogs, metrics, dashboards and alarms for everything above
      IAM / IAM Identity CenterWho can access what. How you sign in.

      Signing in

      Most companies sign you in through single sign-on (IAM Identity Center, formerly AWS SSO) and an access portal, where you pick an account and role. For the command line:

      # once: set up a profile (asks for your portal URL, account and role)
      aws configure sso
      
      # each day, or when your session expires
      aws sso login --profile dev
      
      # check who you are signed in as
      aws sts get-caller-identity --profile dev

      How CloudWatch Logs is organised

      • Log group: all logs for one service or function, e.g. /ecs/workorder-service or /aws/lambda/send-notifications.
      • Log stream: one source inside the group, usually one container or server instance. A service with 4 containers has 4 streams.
      • Log event: one line, with a timestamp.

      In the console: open CloudWatch → Logs → Log groups, pick the group, then use Search all log streams to search every container at once.

      Try it · find out why work order WO-88213 failed

      A dispatcher reports that creating work order WO-88213 showed an error. Here are the service's logs from that minute. Search the way you would in CloudWatch: first for the work order number, then for the request ID you find next to it.

      Logs Insights: querying logs like a database

      CloudWatch → Logs Insights lets you query one or more log groups. Choose the time range at the top, then run queries like these:

      # The 50 most recent errors
      fields @timestamp, @message
      | filter @message like /ERROR/
      | sort @timestamp desc
      | limit 50
      
      # Everything that happened in one request
      fields @timestamp, @message
      | filter @message like /req-7f3a/
      | sort @timestamp asc
      
      # Errors per 5 minutes: did the problem start after a deploy?
      filter @message like /ERROR/
      | stats count(*) as errors by bin(5m)

      From the terminal

      # Stream logs live, like tail -f
      aws logs tail /ecs/workorder-service --follow --profile dev
      
      # Only errors from the last hour
      aws logs tail /ecs/workorder-service --since 1h --filter-pattern ERROR --profile dev

      Tips that save hours

      • Seeing no logs at all? Check the account, the region (top right of the console) and the time range. These three cause most "the logs are empty" moments.
      • Timestamps are usually in UTC. A problem at 3:10 PM in India (IST, UTC+5:30) appears at 09:40 UTC. The console has a UTC/local time toggle.
      • Follow the request ID (also called a correlation or trace ID). It ties together every line from one request, even across services.
      • Not every ERROR is your bug. Busy services always have some background errors. Look for errors that match the time and the request.
      • Treat production logs as sensitive. They can contain customer names and addresses. Don't paste them into public channels; share the request ID and a short summary instead.
      Main takeaways
      1. Each service logs to a log group; each container writes its own stream. Search all streams at once.
      2. Search for the business ID, grab the request ID, then filter by it to see the whole story.
      3. No logs? Check account, region and time range (in UTC) before anything else.
      Part

      Shipping safely

      Chapter 14

      Environments, CI/CD and keeping things running

      Code passes through several copies of the system, called environments, before reaching real users. Each one catches different problems.

      Try it · walk code from laptop to production

      CI/CD

      In plain words: every time you push code, a robot grabs it, builds it from scratch, and runs all the tests. That's CI (Continuous Integration). If everything passes and the code is merged, another robot can deploy it, first to staging, then to production. That's CD (Continuous Delivery or Deployment). The robots never get tired or forget a step, which is why teams trust them more than manual checklists.

      This guide's examples use Bitbucket Pipelines; others include GitHub Actions, GitLab CI, Jenkins and AWS CodePipeline. The pipeline is defined in a file in the repo, so you can read exactly what runs.

      # bitbucket-pipelines.yml  (simplified, for a Maven service)
      image: maven:3.9-eclipse-temurin-21
      
      pipelines:
        pull-requests:
          '**':                           # every PR, whatever the branch name
            - step:
                name: Build and test
                caches: [maven]
                script:
                  - mvn -B verify             # compile, unit + integration tests
        branches:
          main:                           # after a PR merges
            - step:
                name: Build and test
                caches: [maven]
                script:
                  - mvn -B verify
            - step:
                name: Deploy to staging
                deployment: staging
                script:
                  - ./scripts/deploy.sh staging

      Shipping safely

      • Feature flags let you merge unfinished or risky code switched off, then turn it on for 1% of users, then everyone. Turning it off is the fastest rollback.
      • Rollback means redeploying the previous version when a release goes wrong. Fix forward later, calmly.
      • Semantic versioning (MAJOR.MINOR.PATCH, e.g. 2.4.1): bump PATCH for fixes, MINOR for new backwards-compatible features, MAJOR for breaking changes.

      Logs, metrics and alerts

      Once code is live, you can't attach a debugger. You rely on what it records.

      Log levelUse forExample
      DEBUGDetail useful only while developingCart contents: 3 items
      INFONormal, notable eventsOrder 5531 created
      WARNSomething odd, but handledPayment API slow, retrying (attempt 2)
      ERRORSomething failed and needs attentionPayment failed for order 5531: timeout

      Metrics are numbers over time (requests per second, error rate, response time), shown on dashboards like Grafana or Datadog. Alerts page the on-call engineer when a metric crosses a threshold.

      When things break: incidents

      Production problems are called incidents. Healthy teams run blameless postmortems afterwards: they ask "how did our process let this happen?" not "whose fault was it?". If you cause an outage (everyone does eventually), say so quickly. Speed of reporting matters far more than the mistake.

      Main takeaways
      1. Code goes local → dev → staging → production. Each step catches different problems.
      2. CI checks every change automatically; read its config to see what runs.
      3. Log useful events at the right level, and report production mistakes immediately.
      Chapter 15

      Security basics every engineer needs

      Security isn't only the security team's job. Most real breaches start with an everyday mistake. A few habits prevent most of them.

      1. Never commit secrets

      Passwords, API keys, tokens and private keys go in environment variables or a secrets manager (AWS Secrets Manager, Vault), never in code. Keep a .env.example with fake values in the repo, and the real .env in .gitignore.

      If you accidentally push a secret: tell your lead right away and get the key rotated (replaced with a new one). Deleting it in a new commit is not enough, because it stays in Git history and anyone with repo access, or anyone at all if the repo is ever made public, can read it.

      2. Never trust input

      Anything from a user, URL, form or other system could be malicious. Validate it on the server, even if the frontend already checks it. The classic example is SQL injection:

      Injectable
      // name = "x' OR '1'='1"
      // returns every user in the table
      db.query(
        "SELECT * FROM users WHERE name = '"
        + name + "'"
      );
      Parameterised
      // the driver treats name as data,
      // never as SQL
      db.query(
        "SELECT * FROM users WHERE name = $1",
        [name]
      );

      3. More habits

      • Authentication vs authorization. Authentication: who are you? Authorization: are you allowed to do this? Check both on the server for every request. Hiding a button is not security.
      • Least privilege. Give users, services and API keys only the access they need.
      • Escape output. Displaying user text as raw HTML enables XSS (cross-site scripting). Frameworks like React escape by default; avoid bypassing it.
      • Keep dependencies updated. Most vulnerabilities live in outdated libraries. Don't ignore Dependabot or npm audit warnings.
      • Don't log sensitive data. No passwords, card numbers or full personal details in logs.
      • Never store plain-text passwords. Use a proper hashing library (bcrypt, Argon2). Never write your own crypto.

      The OWASP Top 10 is the standard list of the most common web security risks. Skim it once this year.

      Main takeaways
      1. Secrets live in environment variables, never in Git. A leaked key must be rotated.
      2. Validate all input on the server and use parameterised queries.
      3. Check permissions on the server for every request; the UI is not a security layer.
      Part

      Habits and reference

      Chapter 16

      Everyday habits of good engineers

      Most of the job isn't clever algorithms. It's a handful of habits repeated every day. None of these need talent, only practice.

      HabitWhat it looks like
      Read before you writeBefore changing a file, read the code around it and copy how it's done there. Consistency beats personal style.
      Run it locally firstGet the project running on your laptop using the README. Fix the README if a step was missing; that's a great first PR.
      Test your changeRun the app and the tests before pushing. Add a test that would have caught the bug you fixed.
      Ship small and oftenThree small PRs this week beat one giant PR next month.
      Use the 30-minute ruleStuck for 30 minutes with no progress? Write down what you tried and ask.
      Own your mistakesBroke something? Say so early and help fix it. Trust comes from honesty, not from never failing.
      Estimate honestly"I'm not sure, maybe 3 days, I've never touched the payment code" is a great estimate.
      Learn the businessUnderstand who the users are and why features matter. It makes your technical choices better.
      Write things downKeep a work log of commands, gotchas and answers. It's also great material for your performance review.

      How to ask a good question

      Include what you're trying to do, what you expected, what actually happened (the exact error), and what you've already tried.

      Hard to answer
      hey, login isn't working,
      can you help?
      Easy to answer
      Running the app locally, login returns
      401 even with the seed user (test@shop.dev).
      Expected 200. I checked .env has
      AUTH_SECRET set and restarted the server.
      Error log: "invalid signature".
      Is there a step I'm missing?

      Your first 90 days

      • Weeks 1–2: get the project running, read the README and the architecture docs, ship a tiny fix (a typo, a doc update).
      • Weeks 3–6: take small tickets end to end. Review others' PRs to learn the codebase.
      • Weeks 7–12: take a medium feature. Join on-call shadowing if your team has it. Write down three things you'd improve.
      Main takeaways
      1. Match the code that already exists before inventing your own patterns.
      2. Asking for help early, with context, is a professional skill.
      3. Honesty about mistakes and estimates builds more trust than looking perfect.
      Chapter 17 · keep this handy

      Cheat sheet and glossary

      I want to…Command
      Download a projectgit clone <url>
      See what's changed and where it isgit status
      Get the latest code from the teamgit pull
      Start a new branchgit switch -c feature/name
      Move to another branchgit switch main
      See exactly what I changedgit diff / git diff --staged
      Pick changes for the next commitgit add file.js
      Save a commitgit commit -m "Fix login bug"
      Share my branchgit push -u origin feature/name
      Bring main's changes into my branchgit merge main
      See recent commitsgit log --oneline
      See who changed a line and whygit blame file.js
      Throw away edits to one filegit restore file.js
      Unstage a filegit restore --staged file.js
      Fix the last commit (not yet pushed)git commit --amend
      Undo a pushed commit safelygit revert <hash>
      Put work aside for a momentgit stash, later git stash pop
      Back out of a messy mergegit merge --abort
      Find "lost" commitsgit reflog

      Beyond Git

      I want to…Command or query
      Build and test a Java servicemvn clean install
      Run one Java test classmvn test -Dtest=WorkOrderServiceTest
      Start a Spring Boot appmvn spring-boot:run
      Create a Python virtual environmentpython3 -m venv .venv
      Activate it (Mac/Linux · Windows)source .venv/bin/activate · .venv\Scripts\activate
      Install a script's packagespip install -r requirements.txt
      See a script's optionspython script.py --help
      Sign in to AWS from the terminalaws sso login --profile dev
      Follow a service's logs liveaws logs tail /ecs/workorder-service --follow
      Recent errors in Logs Insightsfilter @message like /ERROR/ | sort @timestamp desc
      Join two SQL tablesSELECT … FROM a JOIN b ON b.a_id = a.id
      Find documents in MongoDBdb.workOrders.find({ status: "OPEN" })
      Call an API from the terminalcurl -H "Authorization: Bearer $TOKEN" https://api…/work-orders/88213
      Find my open Jira tickets (JQL)assignee = currentUser() AND resolution = Unresolved

      Glossary · guess first, then tap

      Finished? Go back to any quiz you missed. Wrong answers are where the learning happens, and there's no limit on tries.