Arrays & Strings
The foundation. 80% of problems touch arrays in some way. Know these cold.
Array basics O(1) access
  int[] arr = new int[5];           // fixed size, all 0s
  int[] arr = {1, 2, 3, 4, 5};      // inline init
  arr.length                          // NOT .length() — no parens

  // Iterate
  for (int i = 0; i < arr.length; i++) { }
  for (int num : arr) { }             // enhanced for — use when no index needed

  // 2D array
  int[][] grid = new int[3][4];       // 3 rows, 4 cols
  grid.length                         // rows
  grid[0].length                      // cols

  // Fill
  Arrays.fill(arr, -1);              // fill all with -1
  Arrays.fill(grid[0], 0);           // fill one row

  // Sort
  Arrays.sort(arr);                   // ascending
  Arrays.sort(arr, 0, 5);            // sort subarray [0,5)

  // Copy
  int[] copy = Arrays.copyOf(arr, arr.length);
  int[] sub  = Arrays.copyOfRange(arr, 1, 4); // [1,4)

  // Convert to list (read-only!)
  List<Integer> list = Arrays.asList(1, 2, 3);
⚠ Common trap Arrays.asList() returns a fixed-size list — you CANNOT add/remove from it. Wrap it: new ArrayList<>(Arrays.asList(...)) if you need a mutable list.
Complexity cheatsheet
Know these before you write a single line. Aim for O(n) or O(n log n) in interviews.
Structure / OpTimeNotes
Array accessO(1)By index
Array searchO(n)Linear scan
Array sortO(n log n)Arrays.sort uses dual-pivot quicksort
HashMap get/putO(1) avgO(n) worst — hash collision
HashSet containsO(1) avgSame as HashMap
TreeMap get/putO(log n)Sorted, red-black tree
PriorityQueue add/pollO(log n)Heap operations
Stack push/popO(1)Use Deque, not Stack class
String concat (+)O(n)Use StringBuilder in loops
Binary searchO(log n)Array must be sorted
BFS / DFSO(V + E)V = vertices, E = edges
ArrayList
Dynamic array. Use when you need a resizable array. Your go-to for most problems.
ArrayList — everything you need
  List<Integer> list = new ArrayList<>();

  // Add
  list.add(5);                         // append to end — O(1) amortized
  list.add(0, 99);                    // insert at index 0 — O(n), SLOW

  // Access
  list.get(0);                         // O(1)
  list.set(0, 10);                    // update at index — O(1)
  list.size();                         // NOT .length

  // Remove
  list.remove(0);                     // remove by INDEX — O(n)
  list.remove(Integer.valueOf(5));   // remove by VALUE — wrap int!

  // Search
  list.contains(5);                  // O(n)
  list.indexOf(5);                   // -1 if not found

  // Iterate — 3 ways
  for (int x : list) { }              // enhanced for — cleanest
  for (int i = 0; i < list.size(); i++) { list.get(i); }
  list.forEach(x -> System.out.println(x));

  // Sort
  Collections.sort(list);             // ascending
  list.sort(Comparator.reverseOrder()); // descending
  list.sort((a, b) -> b - a);         // same — descending with lambda

  // Useful ops
  Collections.reverse(list);
  Collections.max(list);
  Collections.min(list);
  Collections.frequency(list, 5);    // count of 5

  // Convert to array
  int[] arr = list.stream().mapToInt(Integer::intValue).toArray();

  // Sublist (view, not copy)
  List<Integer> sub = list.subList(1, 3); // [1,3)
🔴 Critical trap list.remove(1) removes by INDEX. list.remove(Integer.valueOf(1)) removes by VALUE. With int lists this causes bugs that are very hard to spot. Always wrap when removing by value.
HashMap
Your most powerful DSA tool. Frequency counting, memoization, index storing — use HashMap everywhere.
HashMap — complete reference
  Map<String, Integer> map = new HashMap<>();

  // Put / Get
  map.put("key", 1);
  map.get("key");                    // returns null if missing — not 0!
  map.getOrDefault("key", 0);      // safe get — use this always

  // Check
  map.containsKey("key");
  map.containsValue(1);             // O(n) — rarely useful
  map.size();
  map.isEmpty();

  // Remove
  map.remove("key");

  // Frequency count pattern — most common use
  for (char c : s.toCharArray()) {
      map.put(c, map.getOrDefault(c, 0) + 1);
  }
  // Shortcut — same thing
  map.merge(c, 1, Integer::sum);

  // putIfAbsent
  map.putIfAbsent("key", 0);        // only puts if key not present

  // computeIfAbsent — for nested maps / grouping
  map.computeIfAbsent("key", k -> new ArrayList<>()).add("value");

  // Iterate — 3 ways
  for (Map.Entry<String, Integer> e : map.entrySet()) {
      e.getKey(); e.getValue();
  }
  for (String key : map.keySet()) { }
  for (int val : map.values()) { }

  // Sort by value (common interview need)
  map.entrySet().stream()
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
    .forEach(e -> { });
✦ Pattern: Two Sum Store value → index in map. For each num, check if (target - num) exists in map. O(n) instead of O(n²).
✦ Pattern: Anagram check Build freq map for s1, decrement for s2. If all values are 0, they are anagrams. Or just sort both strings and compare.
LinkedHashMap — when order matters
  // LinkedHashMap preserves insertion order
  Map<String, Integer> lmap = new LinkedHashMap<>();

  // LRU Cache trick — access-order LinkedHashMap
  Map<Integer, Integer> lru = new LinkedHashMap<>(16, 0.75f, true) {
      protected boolean removeEldestEntry(Map.Entry e) {
          return size() > capacity;
      }
  };
HashSet
When you need O(1) lookup and don't care about order or count. Duplicate detection, visited tracking.
HashSet — all ops
  Set<Integer> set = new HashSet<>();

  set.add(5);                         // returns false if already exists
  set.contains(5);                   // O(1) — this is the whole point
  set.remove(5);
  set.size();

  // Init from array — very common
  Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));

  // Duplicate check pattern
  boolean hasDup(int[] nums) {
      Set<Integer> seen = new HashSet<>();
      for (int n : nums)
          if (!seen.add(n)) return true; // add returns false if dup
      return false;
  }

  // Longest consecutive sequence — O(n)
  Set<Integer> set = new HashSet<>();
  for (int n : nums) set.add(n);
  for (int n : nums) {
      if (!set.contains(n - 1)) { // start of sequence
          int len = 1;
          while (set.contains(n + len)) len++;
      }
  }
Stack / Deque
Never use Stack class — it's legacy and synchronized. Always use Deque with ArrayDeque.
Deque as Stack (LIFO)
  Deque<Integer> stack = new ArrayDeque<>();

  stack.push(5);                      // add to top
  stack.pop();                        // remove from top — throws if empty
  stack.peek();                       // look at top — throws if empty
  stack.isEmpty();
  stack.size();

  // Safe versions — return null instead of throwing
  stack.offerFirst(5);               // push
  stack.pollFirst();                  // pop — returns null if empty
  stack.peekFirst();                  // peek — returns null if empty

  // Valid parentheses pattern
  Deque<Character> stk = new ArrayDeque<>();
  for (char c : s.toCharArray()) {
      if (c == '(') stk.push(c);
      else if (!stk.isEmpty()) stk.pop();
      else return false;
  }
  return stk.isEmpty();
Deque as Queue (FIFO) — for BFS
  Deque<Integer> queue = new ArrayDeque<>();

  queue.offer(5);                    // enqueue — add to back
  queue.poll();                      // dequeue — remove from front (null if empty)
  queue.peek();                      // look at front

  // Deque as double-ended (sliding window maximum)
  Deque<Integer> dq = new ArrayDeque<>();
  dq.offerFirst(5);   dq.offerLast(3);
  dq.pollFirst();      dq.pollLast();
  dq.peekFirst();      dq.peekLast();
PriorityQueue (Heap)
For Top K problems, shortest path, median finding. Min-heap by default.
PriorityQueue — min heap and max heap
  // Min heap (default) — smallest element at head
  PriorityQueue<Integer> minH = new PriorityQueue<>();

  // Max heap
  PriorityQueue<Integer> maxH = new PriorityQueue<>(Comparator.reverseOrder());
  // or lambda:
  PriorityQueue<Integer> maxH = new PriorityQueue<>((a, b) -> b - a);

  minH.offer(5);                    // add — O(log n)
  minH.poll();                      // remove min — O(log n)
  minH.peek();                      // look at min — O(1)

  // Top K frequent elements pattern
  PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
  for (var e : freqMap.entrySet()) {
      pq.offer(new int[]{e.getKey(), e.getValue()});
      if (pq.size() > k) pq.poll();   // keep only top K
  }

  // Custom object in PQ
  PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
  pq.offer(new int[]{dist, node});
✦ Top K pattern Use min-heap of size K. When size exceeds K, poll (removes smallest). At end, heap contains K largest. This is O(n log k) — better than sorting O(n log n).
TreeMap / TreeSet
Sorted map/set. Use when you need ordered keys or floor/ceiling operations. O(log n) everything.
TreeMap — sorted map with navigation
  TreeMap<Integer, String> tm = new TreeMap<>();
  tm.put(1, "a"); tm.put(3, "c"); tm.put(5, "e");

  tm.firstKey();                     // 1 — smallest
  tm.lastKey();                      // 5 — largest
  tm.floorKey(4);                   // 3 — largest key <= 4
  tm.ceilingKey(4);                 // 5 — smallest key >= 4
  tm.lowerKey(3);                   // 1 — strictly less than 3
  tm.higherKey(3);                  // 5 — strictly greater than 3

  // TreeSet — same but just keys
  TreeSet<Integer> ts = new TreeSet<>();
  ts.floor(4);  ts.ceiling(4);
Java Streams
Not a replacement for loops — a shortcut for filtering, sorting, grouping, aggregating and transforming data. Extremely useful in interviews for collection processing.
⚠ When NOT to use Streams Performance-critical inner loops, BFS, DFS, Sliding Window, Binary Search, DP transitions, index-heavy logic and situations requiring early exit. Streams are best for setup, transformation and output stages.
✦ When Streams shine in DSA Sorting maps by value, frequency counting, filtering, type conversion, grouping, aggregation, Top-K extraction and collection transformations.
Use Case
Sort Map By Value
Top K Frequent Elements, frequency ranking, leaderboards.
Use Case
Type Conversion
int[] ↔ List<Integer>, String[] ↔ List<String>
Use Case
Filter + Collect
Extract only matching elements into a new collection.
Use Case
Group By
Group anagrams, bucket values, build frequency maps.
Use Case
Aggregation
Sum, min, max, average and counts without loops.
Use Case
flatMap
Flatten nested collections and structures.
Sort HashMap by value — ascending and descending Top K / frequency rank
Map<String, Integer> freq = new HashMap<>();

// Sort by value ascending — get entries in order
List<Map.Entry<String, Integer>> asc = freq.entrySet().stream()
    .sorted(Map.Entry.comparingByValue())
    .collect(Collectors.toList());

// Sort by value descending
List<Map.Entry<String, Integer>> desc = freq.entrySet().stream()
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
    .collect(Collectors.toList());

// Get just the keys sorted by value — most common in interviews
List<String> keysByFreq = freq.entrySet().stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());

// Top K keys by frequency
List<String> topK = freq.entrySet().stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
    .limit(k)
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());
Sort HashMap by key TreeMap alternative
// Sort by key ascending (same as using TreeMap)
List<Map.Entry<Integer, String>> byKey = map.entrySet().stream()
    .sorted(Map.Entry.comparingByKey())
    .collect(Collectors.toList());

// Collect back into sorted LinkedHashMap (preserves order)
Map<String, Integer> sorted = freq.entrySet().stream()
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
    .collect(Collectors.toLinkedHashMap(
        Map.Entry::getKey,
        Map.Entry::getValue,
        (e1, e2) -> e1,
        LinkedHashMap::new
    ));
Type conversions — the ones you need constantly int[] ↔ List<Integer>
// int[] → List<Integer>
List<Integer> list = Arrays.stream(arr)
    .boxed()
    .collect(Collectors.toList());

// List<Integer> → int[]
int[] arr = list.stream()
    .mapToInt(Integer::intValue)
    .toArray();

// String[] → List<String>
List<String> strList = Arrays.stream(strArr).collect(Collectors.toList());

// List<String> → String[] 
String[] strArr = list.toArray(String[]::new);

// int[] → Set<Integer> — fast lookup after building from array
Set<Integer> set = Arrays.stream(arr).boxed().collect(Collectors.toSet());

// char[] → String (StringBuilder is faster but this works)
String s = new String(charArray);
// String → char[] then sort → String (anagram canonical form)
char[] c = s.toCharArray(); Arrays.sort(c); String key = new String(c);
Filter + collect Condition-based extraction
// Keep only even numbers
List<Integer> evens = list.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

// Filter and count
long count = list.stream().filter(n -> n > 5).count();

// Filter map entries — keep entries with value > threshold
Map<String, Integer> filtered = freq.entrySet().stream()
    .filter(e -> e.getValue() > 1)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

// distinct — deduplicate
List<Integer> unique = list.stream().distinct().collect(Collectors.toList());
Group by — Collectors.groupingBy Group anagrams pattern
// Group anagrams — canonical form as key
Map<String, List<String>> groups = Arrays.stream(strs)
    .collect(Collectors.groupingBy(s -> {
        char[] c = s.toCharArray();
        Arrays.sort(c);
        return new String(c);
    }));
// groups.values() = List<List<String>> — answer directly

// Group by length
Map<Integer, List<String>> byLen = Arrays.stream(words)
    .collect(Collectors.groupingBy(String::length));

// Group and count (frequency map via streams)
Map<Integer, Long> freqMap = Arrays.stream(nums).boxed()
    .collect(Collectors.groupingBy(n -> n, Collectors.counting()));
Aggregation — sum, max, min, average Without manual loops
int[] nums = {3, 1, 4, 1, 5};

Arrays.stream(nums).sum();             // 14
Arrays.stream(nums).max().getAsInt(); // 5 — returns OptionalInt
Arrays.stream(nums).min().getAsInt(); // 1
Arrays.stream(nums).average().getAsDouble(); // 2.8
Arrays.stream(nums).count();           // 5

// Max by comparator on a list of objects
Optional<String> longest = list.stream()
    .max(Comparator.comparingInt(String::length));
longest.orElse("");                   // safe unwrap

// Sum of values in a map
int total = freq.values().stream().mapToInt(Integer::intValue).sum();

// anyMatch / allMatch / noneMatch — short-circuit
list.stream().anyMatch(n -> n > 10);  // true if any match
list.stream().allMatch(n -> n > 0);  // true if all match
list.stream().noneMatch(n -> n < 0); // true if none match
flatMap — flatten nested structures Adjacency list / multi-word problems
// Flatten List<List<Integer>> → List<Integer>
List<Integer> flat = nested.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());

// All characters from a list of strings
List<Character> chars = words.stream()
    .flatMap(s -> s.chars().mapToObj(c -> (char) c))
    .collect(Collectors.toList());

// All unique characters across all strings
Set<Character> uniqueChars = words.stream()
    .flatMapToInt(String::chars)
    .mapToObj(c -> (char) c)
    .collect(Collectors.toSet());
map — transform each element Extract, convert, apply function
// Extract field from object list
List<String> names = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.toList());

// Transform values — square each element
int[] squared = Arrays.stream(nums)
    .map(n -> n * n)
    .toArray();

// String transformation
List<String> upper = list.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());

// mapToInt — when you need IntStream (for sum/max/etc)
int totalLen = words.stream().mapToInt(String::length).sum();
IntStream.range / rangeClosed — index-based iteration Replaces for loop in stream context
// IntStream.range(start, end) — exclusive end, like for(i=0; i<n; i++)
IntStream.range(0, 5).forEach(i -> System.out.println(i));

// Build a list of indices where condition is true
List<Integer> indices = IntStream.range(0, nums.length)
    .filter(i -> nums[i] % 2 == 0)
    .boxed()
    .collect(Collectors.toList());

// Generate a frequency array as a map with stream
Map<Integer, Long> freq = IntStream.of(nums).boxed()
    .collect(Collectors.groupingBy(n -> n, Collectors.counting()));

// rangeClosed — inclusive end, like for(i=1; i<=n; i++)
IntStream.rangeClosed(1, 10).sum(); // 55
🔴 Streams do NOT short-circuit forEach You cannot break from forEach. Use anyMatch(), findFirst(), or a normal loop when early exit is required.
✦ Streams vs Loops Streams are excellent for transformations, grouping, filtering, sorting and aggregation. Regular loops are better for index manipulation, mutation, early exit and performance-critical code.
Sliding Window
When the problem asks for a subarray/substring that satisfies some condition. Converts O(n²) to O(n).
✦ When to use Keywords: "subarray", "substring", "contiguous", "window", "longest", "shortest", "maximum sum". The window expands right and shrinks from left.
Fixed window size
  // Max sum of subarray of size k
  int sum = 0, max = 0;
  for (int i = 0; i < nums.length; i++) {
      sum += nums[i];
      if (i >= k) sum -= nums[i - k];  // shrink from left
      if (i >= k - 1) max = Math.max(max, sum);
  }
Variable window — longest with condition
  // Longest substring without repeating characters
  Map<Character, Integer> map = new HashMap<>();
  int left = 0, max = 0;
  for (int right = 0; right < s.length(); right++) {
      char c = s.charAt(right);
      if (map.containsKey(c))
          left = Math.max(left, map.get(c) + 1); // shrink
      map.put(c, right);
      max = Math.max(max, right - left + 1);
  }
Two Pointers
Two indices moving toward each other or in the same direction. Sorted arrays, pair finding.
✦ When to use Sorted array + find pair/triplet with target sum. Remove duplicates in-place. Merge two sorted arrays. Palindrome check.
Opposite direction — two sum sorted
  int left = 0, right = nums.length - 1;
  while (left < right) {
      int sum = nums[left] + nums[right];
      if      (sum == target) return new int[]{left, right};
      else if (sum < target)  left++;
      else                    right--;
  }
Same direction — remove duplicates in-place
  int slow = 0;
  for (int fast = 1; fast < nums.length; fast++) {
      if (nums[fast] != nums[slow])
          nums[++slow] = nums[fast];
  }
  return slow + 1; // new length
Fast & Slow Pointers
Floyd's cycle detection. For linked list problems — cycle, middle node, duplicate number.
Cycle detection + middle of linked list
  // Detect cycle
  ListNode slow = head, fast = head;
  while (fast != null && fast.next != null) {
      slow = slow.next;
      fast = fast.next.next;
      if (slow == fast) return true; // cycle!
  }
  return false;

  // Find middle (slow is at middle when fast reaches end)
  ListNode slow = head, fast = head;
  while (fast != null && fast.next != null) {
      slow = slow.next;
      fast = fast.next.next;
  }
  // slow is now at middle
Binary Search
Not just for sorted arrays. Binary search on the ANSWER is a powerful pattern you must know.
Standard binary search template
  int left = 0, right = nums.length - 1;
  while (left <= right) {              // <= not <
      int mid = left + (right - left) / 2; // avoid overflow — never (l+r)/2
      if      (nums[mid] == target) return mid;
      else if (nums[mid] < target)  left = mid + 1;
      else                          right = mid - 1;
  }
  return -1;
Binary search on answer — find minimum valid value
  // "What is the minimum X such that condition(X) is true?"
  // Example: Koko eating bananas, capacity ship packages
  int left = minPossible, right = maxPossible;
  while (left < right) {               // < not <=
      int mid = left + (right - left) / 2;
      if (canDo(mid)) right = mid;       // mid works, try smaller
      else           left = mid + 1;    // mid too small
  }
  return left;
🔴 Never write (left + right) / 2 It overflows for large values. Always use left + (right - left) / 2. This is a classic interview trap.
BFS / DFS
BFS = shortest path, level order. DFS = all paths, cycle detection, connected components.
BFS — level order / shortest path
  Deque<Integer> queue = new ArrayDeque<>();
  Set<Integer> visited = new HashSet<>();
  queue.offer(start);
  visited.add(start);
  int level = 0;

  while (!queue.isEmpty()) {
      int size = queue.size();           // process level by level
      for (int i = 0; i < size; i++) {
          int node = queue.poll();
          if (node == target) return level;
          for (int nei : graph.get(node)) {
              if (!visited.contains(nei)) {
                  visited.add(nei);
                  queue.offer(nei);
              }
          }
      }
      level++;
  }
DFS — recursive and iterative
  // Recursive DFS
  void dfs(int node, Set<Integer> visited) {
      visited.add(node);
      for (int nei : graph.get(node)) {
          if (!visited.contains(nei))
              dfs(nei, visited);
      }
  }

  // Grid DFS (islands pattern)
  int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
  void dfs(char[][] grid, int r, int c) {
      if (r < 0 || r >= grid.length || c < 0 ||
          c >= grid[0].length || grid[r][c] != '1') return;
      grid[r][c] = '0';                 // mark visited by mutating
      for (int[] d : dirs)
          dfs(grid, r + d[0], c + d[1]);
  }
Dynamic Programming
You already have 50 DP problems solved. This is a refresh of the core patterns.
Pattern
1D DP
dp[i] depends on dp[i-1] or dp[i-2]. Climbing stairs, house robber, coin change.
dp[i] = dp[i-1] + dp[i-2]
Pattern
2D DP
dp[i][j] depends on previous row/col. Unique paths, edit distance, LCS.
dp[i][j] = dp[i-1][j] + dp[i][j-1]
Pattern
Knapsack
0/1 choices at each step. Subset sum, partition equal subset, target sum.
dp[i][w] = max(dp[i-1][w], val+dp[i-1][w-wt])
Pattern
String DP
Comparing two strings. LCS, edit distance, palindrome substrings.
if s1[i]==s2[j]: dp[i][j]=dp[i-1][j-1]+1
DP template — bottom up
  // Coin change — minimum coins to make amount
  int[] dp = new int[amount + 1];
  Arrays.fill(dp, amount + 1);        // fill with "infinity"
  dp[0] = 0;
  for (int a = 1; a <= amount; a++)
      for (int c : coins)
          if (c <= a)
              dp[a] = Math.min(dp[a], dp[a - c] + 1);
  return dp[amount] > amount ? -1 : dp[amount];
Monotonic Stack
Next greater/smaller element problems. Histogram area. Daily temperatures. O(n) solution to O(n²) problems.
✦ When to use "Next greater element", "previous smaller element", "largest rectangle in histogram", "trapping rain water".
Next greater element
  int[] result = new int[nums.length];
  Arrays.fill(result, -1);
  Deque<Integer> stack = new ArrayDeque<>(); // stores indices

  for (int i = 0; i < nums.length; i++) {
      while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
          result[stack.pop()] = nums[i]; // found next greater
      }
      stack.push(i);
  }
Sorting tricks
Custom sort — lambdas
  // Sort int array descending — must use Integer[]
  Integer[] arr = {3,1,4,1,5};
  Arrays.sort(arr, (a, b) -> b - a);

  // Sort 2D array by first element
  Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

  // Sort by length then alphabetically
  Arrays.sort(words, (a, b) ->
      a.length() != b.length() ? a.length() - b.length() : a.compareTo(b));

  // WARNING: b - a overflows with large negatives. Safe version:
  (a, b) -> Integer.compare(b, a)
String tricks
String operations — all the ones that matter
  String s = "hello";

  s.charAt(0);                        // 'h'
  s.length();                         // 5 — with parens, unlike array
  s.substring(1, 3);                  // "el" — [1,3)
  s.indexOf('l');                     // 2 — first occurrence
  s.contains("ell");
  s.startsWith("he");
  s.endsWith("lo");
  s.toCharArray();                    // char[] — use in loops
  s.split(",");                       // String[]
  s.trim();                           // remove leading/trailing spaces
  s.toLowerCase(); s.toUpperCase();
  s.replace('l', 'r');              // replaces ALL
  String.valueOf(42);                 // int to String
  Integer.parseInt("42");            // String to int

  // Char arithmetic
  char c = 'a';
  c - 'a';                            // 0 — index in alphabet
  (char)(c + 1);                      // 'b'
  Character.isLetter(c);
  Character.isDigit(c);
  Character.toLowerCase(c);

  // StringBuilder — use in loops, not + concatenation
  StringBuilder sb = new StringBuilder();
  sb.append("hi");
  sb.reverse();
  sb.deleteCharAt(0);
  sb.toString();

  // Frequency array (only lowercase letters)
  int[] freq = new int[26];
  for (char c : s.toCharArray()) freq[c - 'a']++;
  // Faster than HashMap for lowercase-only problems
🔴 Never use == to compare strings s1 == s2 compares references. Always use s1.equals(s2). This trips people up constantly in interviews.
Math tricks
Math operations you'll actually need
  Math.max(a, b);  Math.min(a, b);
  Math.abs(a);
  Math.pow(2, 10);                    // returns double
  Math.sqrt(25);                      // returns double
  (int) Math.sqrt(25);                // cast to int when needed

  // Integer limits — use these, not magic numbers
  Integer.MAX_VALUE;                   // 2^31 - 1 = 2147483647
  Integer.MIN_VALUE;                   // -2^31 = -2147483648
  Long.MAX_VALUE;                      // when int overflows

  // Modulo (always positive)
  ((a % m) + m) % m;                   // safe mod for negative numbers

  // Even / Odd
  n % 2 == 0;                         // even
  (n & 1) == 0;                        // even — bitwise, slightly faster

  // Divide rounding up
  (int) Math.ceil((double) a / b);
  (a + b - 1) / b;                    // integer ceiling division

  // Bit tricks
  n & (n - 1);                        // removes lowest set bit (check power of 2: == 0)
  n & (-n);                            // isolates lowest set bit
  Integer.bitCount(n);                // count set bits
Interview habits
The meta-skills that separate candidates who know the same things.
✦ Before writing any code Repeat the problem in your own words. Give 1–2 examples including edge cases. State your approach and time/space complexity BEFORE coding. Ask about constraints (array size, negative numbers, duplicates).
✦ Edge cases to always mention Empty input. Single element. All same elements. Negative numbers. Integer overflow. Already sorted. Reverse sorted.
⚠ Pattern recognition — ask yourself Is it sorted? → Binary search or two pointers. Subarray/substring? → Sliding window. Tree/graph? → BFS or DFS. Optimal substructure? → DP. Next greater/smaller? → Monotonic stack. Top K? → Heap.
🔴 Common mistakes in interviews Off-by-one in binary search. Not checking null/empty. Using int when long needed. Modifying input array without asking. Not handling the case where answer doesn't exist. Returning wrong type.
✦ When you're stuck — say this out loud "Let me think about the brute force first." Then optimize. Interviewers want to see your thinking, not silence.