Intermediate
Progress
1/7
Module 1The Fundamental Choice

Arrays vs ArrayLists โ€” When to Use Which

ARRAY = Fixed locker row โ€” rent 20 slots, use 20 or waste them. ARRAYLIST = Expanding backpack โ€” magically grows as you stuff items in. Pick the right one and your code gets faster and cleaner.

The Mental Model

๐Ÿ—„๏ธ
Array
int[], String[], Item[]
โœ…Fixed size at creation
โœ…Fastest โ€” zero overhead
โœ…Works with primitives: int[]
โŒCan't add/remove elements
โŒNo built-in search/sort methods
๐ŸŽ’
ArrayList
ArrayList<String>, List<Item>
โœ…Grows/shrinks dynamically
โœ…add(), remove(), contains()
โœ…Works great with streams
โŒObjects only (no primitives)
โŒ~6x memory overhead vs int[]

Quick Decision Guide

ScenarioUseWhy
Known max size (inventory slots, tile map)ArrayZero overhead, cache-friendly
Unknown/growing size (loot drops, particles)ArrayListAuto-resize, convenient methods
High-frequency add/remove (bullets, particles)Array + poolingNo allocation pressure
Need contains(), indexOf(), remove(Object)ArrayListBuilt-in search/remove
Primitive types in hot path (int[], float[])ArrayNo boxing/unboxing overhead

Side-by-Side: Player Inventory

InventoryComparison.java
// ARRAY โ€” Fixed 20 slots (classic RPG style)
String[] arrayInv = new String[20];
arrayInv[0] = "Iron Sword";
arrayInv[1] = "Health Potion";
// arrayInv[20] = "Shield"; // ๐Ÿ’ฅ ArrayIndexOutOfBoundsException!

// ARRAYLIST โ€” Grows as needed (modern RPG)
ArrayList<String> listInv = new ArrayList<>();
listInv.add("Iron Sword");
listInv.add("Health Potion");
listInv.add("Shield");        // Auto-expands!
listInv.add(0, "Wooden Bow"); // Insert at front, shifts others right
Quick Check
You're building a particle system that spawns and destroys thousands of particles per second. Which should you use?
High-frequency add/remove creates garbage collection pressure. An array-backed object pool reuses particle objects instead of creating/destroying them every frame โ€” critical for smooth 60fps gameplay.
1 of 7
Module 2Fixed-Size Power

Arrays Deep Dive โ€” Fixed-Size Power

Arrays are the bedrock of Java. Contiguous memory, zero overhead, and direct index access make them the fastest collection in the language. Every tile map, frame buffer, and hot-path data structure starts here.

Declaration Patterns

ArrayDeclaration.java
// 1. Declare + allocate (all null/0/false by default)
Item[] equipment = new Item[5];          // 5 null references
int[] stats = new int[6];                // {0,0,0,0,0,0} STR,DEX,INT,WIS,CON,CHA
boolean[] flags = new boolean[32];       // 32 false flags

// 2. Declare + initialize inline
String[] classes = {"Warrior", "Mage", "Rogue", "Cleric"};
int[] dmgPerLevel = {5, 10, 15, 20, 25, 30};
float[] knockback = {0.5f, 1.0f, 1.5f, 2.0f};

// 3. Anonymous array (pass directly to a method)
spawnEnemies(new String[]{"Goblin", "Orc", "Troll"});

๐ŸŽฎ Example 1: Tile Map (2D Array)

๐ŸŽฎ Game Dev Example
TileMap.java
// Legend: 0=Grass, 1=Wall, 2=Water, 3=Chest, 4=Enemy
int[][] dungeonFloor = {
    {1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,2,0,0,3,0,1},
    {1,0,1,0,2,0,1,0,0,1},
    {1,0,0,0,0,0,0,0,4,1},
    {1,1,1,1,1,1,1,1,1,1}
};

final String[] TILES = {"๐ŸŸข","๐Ÿงฑ","๐ŸŒŠ","๐Ÿ“ฆ","๐Ÿ‘น"};

void renderMap(int[][] map, int playerX, int playerY) {
    for (int y = 0; y < map.length; y++) {
        for (int x = 0; x < map[y].length; x++) {
            if (x == playerX && y == playerY)
                System.out.print("๐Ÿง ");
            else
                System.out.print(TILES[map[y][x]] + " ");
        }
        System.out.println();
    }
}
// ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ
// ๐Ÿงฑ ๐ŸŸข ๐ŸŸข ๐ŸŸข ๐ŸŒŠ ๐ŸŸข ๐ŸŸข ๐Ÿ“ฆ ๐ŸŸข ๐Ÿงฑ
// ๐Ÿงฑ ๐ŸŸข ๐Ÿงฑ ๐ŸŸข ๐ŸŒŠ ๐ŸŸข ๐Ÿงฑ ๐ŸŸข ๐ŸŸข ๐Ÿงฑ
// ๐Ÿงฑ ๐ŸŸข ๐ŸŸข ๐ŸŸข ๐ŸŸข ๐ŸŸข ๐ŸŸข ๐ŸŸข ๐Ÿ‘น ๐Ÿงฑ
// ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ ๐Ÿงฑ

๐ŸŽฎ Example 2: Stat Growth Table

๐ŸŽฎ Game Dev Example
StatTable.java
// [Level][STR, DEX, INT, HP, MP]
int[][] growthTable = {
    {  10,  8,  6, 100, 20}, // Level 1
    {  12,  9,  7, 120, 25}, // Level 2
    {  14, 10,  8, 145, 30}, // Level 3
    {  17, 12, 10, 175, 40}, // Level 4
    {  20, 14, 12, 210, 50}  // Level 5
};

void printStatBlock(int level) {
    int[] stats = growthTable[level - 1];
    String[] names = {"STR","DEX","INT","HP ","MP "};
    System.out.println("=== LEVEL " + level + " BASE STATS ===");
    for (int i = 0; i < stats.length; i++)
        System.out.println(names[i] + ": " + stats[i]);
}

โšก Array Utilities (java.util.Arrays)

Arrays.sort(scores)Sort array in-place โ€” ascending order
Arrays.binarySearch(arr, val)Search sorted array โ€” returns index or negative
Arrays.copyOf(arr, newLen)Copy first N elements into new array
Arrays.copyOfRange(arr, i, j)Copy slice from index i to j-1
Arrays.fill(arr, value)Set all elements to a value โ€” great for reset
Arrays.toString(arr)Print-friendly string: "[1, 2, 3]"
Arrays.equals(a, b)Content comparison (not reference!)
Quick Check
What happens if you try to access myArray[myArray.length]?
Arrays are 0-indexed. If length is 5, valid indices are 0-4. Accessing index 5 (length) throws ArrayIndexOutOfBoundsException โ€” one of the most common Java bugs. The last element is always at arr[arr.length - 1].
2 of 7
Module 3Dynamic Collections

ArrayLists Unleashed โ€” Dynamic Collections

When your data grows and shrinks โ€” quest logs, active enemies, inventory with no fixed cap โ€” ArrayList is your tool. It wraps an array internally, automatically resizing by 1.5x when full.

Creation Patterns

ArrayListCreation.java
// 1. Empty, default initial capacity (10)
ArrayList<String> loot = new ArrayList<>();

// 2. With initial capacity (avoids early resizes)
ArrayList<Particle> particles = new ArrayList<>(500);

// 3. From an existing collection
var items = List.of("Sword", "Shield", "Potion");
ArrayList<String> inv = new ArrayList<>(items);

// 4. Diamond operator (Java 7+) โ€” type inferred from left side
ArrayList<Quest> questLog = new ArrayList<>();

Core Methods

list.add(item)Append to end
list.add(index, item)Insert at index, shifts everything right
list.get(index)Get element at index (like arr[i])
list.set(index, item)Replace element at index
list.remove(index)Remove by index, shifts everything left
list.remove(Object)Remove by value (uses .equals())
list.contains(item)Returns true/false โ€” linear search O(n)
list.size()Current number of elements (not capacity)
list.clear()Remove all elements, keep capacity
list.isEmpty()Returns true if size() == 0

๐ŸŽฎ Example 1: Quest Log System

๐ŸŽฎ Game Dev Example
QuestLog.java
record Quest(String id, String title, boolean completed, int xpReward) {}

ArrayList<Quest> questLog = new ArrayList<>();

void addQuest(String id, String title, int xp) {
    questLog.add(new Quest(id, title, false, xp));
    System.out.println("๐Ÿ“œ New Quest: " + title);
}

void completeQuest(String id) {
    for (Quest q : questLog) {
        if (q.id().equals(id) && !q.completed()) {
            int idx = questLog.indexOf(q);
            questLog.set(idx, new Quest(q.id(), q.title(), true, q.xpReward()));
            System.out.println("โœ… Completed: " + q.title() + " (+" + q.xpReward() + " XP)");
            return;
        }
    }
}

void showActiveQuests() {
    System.out.println("๐Ÿ“‹ ACTIVE QUESTS:");
    for (Quest q : questLog)
        if (!q.completed())
            System.out.println("  โ–ก " + q.title() + " [" + q.xpReward() + " XP]");
}

// Usage:
addQuest("q1", "Kill 10 Rats", 100);
addQuest("q2", "Find Lost Amulet", 250);
addQuest("q3", "Defeat Goblin Chief", 500);
completeQuest("q1");
showActiveQuests();
โš ๏ธ
SAFE BACKWARD REMOVAL

When removing items from a list while iterating, always iterate backwards: for (int i = list.size()-1; i >= 0; i--). Forward removal shifts indices and causes skipped elements or exceptions.

Quick Check
What is the difference between list.remove(2) and list.remove("sword")?
remove(int index) removes the element at that position. remove(Object o) searches for the first occurrence using .equals() and removes it. For ArrayList<Integer>, use remove(Integer.valueOf(2)) to remove the value 2, not index 2.
3 of 7
Module 4Search, Sort & Transform

Common Operations โ€” Search, Sort & Transform

The real power of collections comes from operating on them. Sort a leaderboard in one line, filter loot by rarity with streams, convert a grid into an adjacency list for pathfinding โ€” this is where game data comes alive.

๐ŸŽฎ Example 1: Leaderboard Sorting

๐ŸŽฎ Game Dev Example
Leaderboard.java
record PlayerScore(String name, int score, int level, String class_) {}

ArrayList<PlayerScore> leaderboard = new ArrayList<>(List.of(
    new PlayerScore("Arcane",     98750, 45, "Mage"),
    new PlayerScore("BladeMaster", 87650, 42, "Warrior"),
    new PlayerScore("ShadowStep",  87650, 40, "Rogue"),
    new PlayerScore("HolyLight",   75300, 38, "Cleric")
));

// Multi-key sort: score desc โ†’ level desc โ†’ name asc
leaderboard.sort(Comparator
    .comparingInt((PlayerScore p) -> -p.score())
    .thenComparingInt(p -> -p.level())
    .thenComparing(PlayerScore::name)
);

for (int i = 0; i < leaderboard.size(); i++) {
    PlayerScore p = leaderboard.get(i);
    String medal = switch(i) {case 0->"๐Ÿฅ‡";case 1->"๐Ÿฅˆ";case 2->"๐Ÿฅ‰";default->"  ";};
    System.out.printf("%s #%-2d %-15s %,7d XP | Lv%-2d | %s%n",
        medal, i+1, p.name(), p.score(), p.level(), p.class_());
}
// ๐Ÿฅ‡ #1  Arcane           98,750 XP | Lv45 | Mage
// ๐Ÿฅˆ #2  BladeMaster      87,650 XP | Lv42 | Warrior
// ๐Ÿฅ‰ #3  ShadowStep       87,650 XP | Lv40 | Rogue

๐ŸŽฎ Example 2: Loot Filter with Streams (Java 8+)

๐ŸŽฎ Game Dev Example
LootFilter.java
record Item(String name, String type, int rarity, int value) {}
// rarity: 0=Common, 1=Uncommon, 2=Rare, 3=Epic, 4=Legendary

ArrayList<Item> allLoot = new ArrayList<>(List.of(
    new Item("Rusty Sword",  "Weapon",     0,     10),
    new Item("Health Potion","Consumable", 0,     25),
    new Item("Flame Blade",   "Weapon",     3,   5000),
    new Item("Dragon Scale",  "Material",   4, 50000)
));

// Filter: Only Rare+ weapons worth over 1000 gold
List<Item> valuableWeapons = allLoot.stream()
    .filter(i -> i.type().equals("Weapon"))
    .filter(i -> i.rarity() >= 2)
    .filter(i -> i.value() > 1000)
    .sorted(Comparator.comparingInt(Item::value).reversed())
    .toList();

for (Item i : valuableWeapons) {
    String[] icons = {"โšช", "๐ŸŸข", "๐Ÿ”ต", "๐ŸŸฃ", "๐ŸŸ "};
    System.out.println("  " + icons[i.rarity()] + " " + i.name() + " โ€” " + i.value() + "g");
}
// ๐ŸŸฃ Flame Blade โ€” 5000g
๐Ÿ’ก
STREAMS ARE PIPELINES

Streams don't modify the original list. Each .filter() creates a new stream, and .toList() collects the final result. Chain as many operations as you need โ€” it reads like plain English.

Quick Check
You're sorting enemies by HP descending so the strongest attacks first. Which Comparator is correct?
Comparator.comparingInt(Enemy::hp) sorts ascending (lowest HP first). Adding .reversed() flips it to descending (highest HP first). The third option would also work but also sorts ascending.
4 of 7
Module 5Grids & Uneven Data

2D & Jagged Arrays โ€” Grids & Uneven Data

2D arrays power every tile map, dungeon floor, and pixel buffer in game dev. Jagged arrays โ€” where each row has a different length โ€” are perfect for skill trees, dialog trees, and non-uniform data structures.

Rectangular 2D

All rows same length

int[5][10] โ€” 5 rows, 10 cols

Tile maps, images, game boards

Jagged Arrays

Each row can differ in length

int[][] with varying rows

Skill trees, dialog trees, loot tables

๐ŸŽฎ Example 1: Skill Tree (Jagged Array)

๐ŸŽฎ Game Dev Example
SkillTree.java
// Each tier has different number of skills!
String[][] skillTree = {
    {"Slash"},                            // Tier 1: 1 skill
    {"Power Strike", "Cleave"},           // Tier 2: 2 skills
    {"Whirlwind", "Execute", "Rend"},    // Tier 3: 3 skills
    {"Bladestorm", "Annihilate"}         // Tier 4: 2 skills
};

for (int tier = 0; tier < skillTree.length; tier++) {
    System.out.print("Tier " + (tier+1) + ": ");
    for (int s = 0; s < skillTree[tier].length; s++) {
        System.out.print("[" + skillTree[tier][s] + "] ");
    }
    System.out.println("  (Requires " + (tier * 5) + " SP)");
}
// Tier 1: [Slash]  (Requires 0 SP)
// Tier 2: [Power Strike] [Cleave]  (Requires 5 SP)
// Tier 3: [Whirlwind] [Execute] [Rend]  (Requires 10 SP)

๐ŸŽฎ Example 2: Dialog Tree (NPC Conversation)

๐ŸŽฎ Game Dev Example
DialogTree.java
record DialogNode(String npcText, String[] playerOptions, int[] nextNodes) {}

DialogNode[] dialog = {
    // Node 0: Greeting
    new DialogNode(
        "Ah, an adventurer! Need supplies?",
        new String[]{"Buy", "Sell", "Rumors", "Goodbye"},
        new int[]{1, 2, 3, -1}
    ),
    // Node 1: Shop
    new DialogNode(
        "Take a look at my wares!",
        new String[]{"Back"},
        new int[]{0}
    ),
    // Node 2: Rumors โ€” leads to quest hook (node 4)
    new DialogNode(
        "Heard goblins in the north cave...",
        new String[]{"Tell me more", "Back"},
        new int[]{4, 0}
    )
};

void runDialog() {
    int current = 0;
    while (current != -1) {
        DialogNode node = dialog[current];
        System.out.println("\n๐Ÿ’ฌ " + node.npcText());
        for (int i = 0; i < node.playerOptions().length; i++)
            System.out.println("  [" + (i+1) + "] " + node.playerOptions()[i]);
        if (node.playerOptions().length == 0) break;
        int choice = getInput(1, node.playerOptions().length) - 1;
        current = node.nextNodes()[choice];
    }
}
๐ŸŸก Medium Challenge

Dungeon Room Map

Create a jagged array representing dungeon floors where each floor has a different number of rooms:

  • Floor 1: 3 rooms ("Entrance", "Hall", "Armory")
  • Floor 2: 4 rooms ("Barracks", "Kitchen", "Library", "Vault")
  • Floor 3: 2 rooms ("Boss Chamber", "Escape Tunnel")
  • Print each floor and its rooms with nested loops
  • Bonus: mark the last room on each floor as the "boss room"
5 of 7
Module 6Under the Hood

Performance & Memory โ€” Under the Hood

Understanding memory layout is what separates a 30fps game from a 60fps one. Arrays and ArrayLists have vastly different memory costs โ€” and in game loops that run thousands of times per second, that difference matters enormously.

Memory Layout Comparison

int[5] โ€” 4 bytes ร— 5 = 20 bytes, contiguous
Array int[5]
42
18
7
99
3
โ† contiguous, CPU cache loves this
ArrayList<Integer> โ€” each Integer is a 16-byte heap object
ArrayList refs
โ†’obj
โ†’obj
โ†’obj
โ†’obj
โ†’obj
โ€”
โ€”
โ€”
โ€”
โ€”
โ† 5 used, 5 spare slots (default 10 capacity)

int[1000] = 4,000 bytes. ArrayList<Integer>(1000) โ‰ˆ 24,000+ bytes (6ร— overhead from boxing)

๐ŸŽฎ Object Pooling Pattern (Critical for Games)

๐ŸŽฎ Game Dev Pattern
ObjectPool.java
class Pool<T> {
    private final ArrayList<T> available = new ArrayList<>();
    private final ArrayList<T> active    = new ArrayList<>();
    private final Supplier<T> factory;
    
    Pool(Supplier<T> factory, int prewarm) {
        this.factory = factory;
        for (int i = 0; i < prewarm; i++) available.add(factory.get());
    }
    
    T acquire() {
        T e = available.isEmpty() 
            ? factory.get()  // Create new only if needed
            : available.remove(available.size() - 1); // Reuse!
        active.add(e);
        return e;
    }
    
    void release(T e) {
        active.remove(e);  // Remove from active
        // e.reset() โ€” reset state here
        available.add(e);  // Return to pool
    }
}

// Usage in game loop โ€” ZERO allocation after warmup!
Pool<Bullet> bullets = new Pool<>(Bullet::new, 200);
Pool<Enemy>  enemies = new Pool<>(Goblin::new, 50);

void spawnBullet(float x, float y, float dir) {
    Bullet b = bullets.acquire();
    b.init(x, y, dir);
}
โšก
PERFORMANCE RULES OF THUMB

Use int[] / float[] for hot-path data (particle positions, vertex data). Pre-size ArrayLists with expected capacity. Object pools eliminate GC pauses. Profile before optimizing โ€” measure, don't guess.

Quick Check
Why is int[] faster than ArrayList<Integer> for a hot loop?
Auto-boxing converts each int to an Integer object on the heap (16+ bytes each). An int[1000] is 4KB of contiguous memory the CPU cache can devour. ArrayList<Integer> with 1000 elements is 24KB+ scattered on the heap.
6 of 7
Module 7Capstone Project

Capstone โ€” RPG Inventory Manager

The ultimate test. Build a full inventory system combining arrays, ArrayLists, enums, records, sorting, and streams โ€” exactly how a real game's item system works. Equipment slots, rarity tiers, stacking, and value queries all in one.

System Architecture

๐Ÿ“ฆ
Item record
id, name, Rarity enum, Slot enum, value, stats Map, maxStack
๐Ÿ—‚๏ธ
Stack record
Wraps Item + count. Supports add/remove operations immutably
๐ŸŽ’
Inventory class
ArrayList<Stack> bag + EnumMap<Slot, Stack> equipped
๐Ÿ”
Stream queries
Filter by rarity, sort by value, calculate total worth

Data Model

DataModel.java
enum Rarity { COMMON, UNCOMMON, RARE, EPIC, LEGENDARY }
enum Slot   { WEAPON, HELMET, CHEST, GLOVES, BOOTS, RING, AMULET, CONSUMABLE }

record Item(
    String id, String name, Rarity rarity, Slot slot,
    int value, Map<String, Integer> stats, int maxStack
) {
    static final Map<Rarity, String> ICONS = Map.of(
        COMMON, "โšช", UNCOMMON, "๐ŸŸข", RARE, "๐Ÿ”ต",
        EPIC, "๐ŸŸฃ", LEGENDARY, "๐ŸŸ "
    );
    String icon() { return ICONS.get(rarity); }
}

record Stack(Item item, int count) {
    boolean canAdd(int amount) { return count + amount <= item.maxStack(); }
    Stack   add   (int amount) { return new Stack(item, count + amount); }
    Stack   remove(int amount) { return new Stack(item, count - amount); }
}

Core Inventory Operations

Inventory.java
class Inventory {
    private final int capacity;
    private final ArrayList<Stack> items = new ArrayList<>();
    private final Map<Slot, Stack> equipped = new EnumMap<>(Slot.class);
    
    boolean add(Item item, int count) {
        // Try to merge with existing stack first
        for (int i = 0; i < items.size(); i++) {
            Stack s = items.get(i);
            if (s.item().id().equals(item.id()) && s.canAdd(count)) {
                items.set(i, s.add(count));
                return true;
            }
        }
        if (items.size() >= capacity) return false; // Full!
        items.add(new Stack(item, count));
        return true;
    }
    
    // Stream-powered queries
    List<Stack> getByRarity(Rarity minRarity) {
        return items.stream()
            .filter(s -> s.item().rarity().ordinal() >= minRarity.ordinal())
            .sorted(Comparator.comparing(s -> -s.item().value()))
            .toList();
    }
    
    int totalValue() {
        return items.stream()
            .mapToInt(s -> s.item().value() * s.count())
            .sum();
    }
}

Quick Reference Cheatsheet

Arrays
int[] arr = new int[10]Fixed-size, all zeros
arr.lengthSize (not a method call)
Arrays.sort(arr)In-place sort, ascending
Arrays.copyOf(arr, n)New array with first n elements
ArrayLists
new ArrayList<T>(cap)Pre-sized to avoid resizes
list.size()Current count (method call)
list.add/remove/set/getCore CRUD operations
list.sort(Comparator)Sort with custom logic
Decision Guide
Primitives, hot pathArray โ€” no boxing overhead
Unknown/growing sizeArrayList โ€” auto-resize
High-freq spawn/destroyArray + object pool
Filter/sort/transformArrayList + Streams API

Practice Exercises

1
Spell Hotbar โ€” String[10], swap, cooldown display
Array basics15 min
2
Dynamic Party โ€” ArrayList<Character>, add/remove/sort by HP
ArrayList CRUD20 min
3
Loot Table โ€” Map<Enemy, Item[]>, weighted random drop
Arrays in Map25 min
4
Skill Tree UI โ€” Jagged String[][], unlock logic
Jagged arrays25 min
5
Pathfinding Grid โ€” int[][] โ†’ adjacency List<List<Integer>>
Conversion30 min
6
Object Pool โ€” Pool<Bullet> with active/available lists
Pooling pattern35 min
7
Full Inventory โ€” Extend Capstone: drag-drop, save/load JSON
ALL60+ min
๐Ÿš€
NEXT STEPS

You've built the foundation. Next: combine Arrays + Loops for game systems, explore HashMaps for O(1) item lookups, and Generics to make your Pool and Inventory work with any type.

7 of 7
Roadmap