← Back to Resources

Reference

Common Compiler Errors with Fixes

40 errors across 5 languages, every one paired with the verbatim compiler output, root cause, and the Broken vs Fixed snippet that resolves it.

Common Compiler Errors: visual summary card with sections and FAQ counts
4.2k Words
47 Sections
83 Code Snippets
6 Languages

Overview

What this guide covers

40 error messages students hit every week, grouped by error class. Syntax errors reject before code runs. Type errors reject at compile time on typed languages, at runtime on Python and JavaScript. Runtime errors reach execution. Linker errors compile clean but fail before main runs. Undefined behavior compiles, runs, then misbehaves silently. Each entry shows the actual compiler output (GCC, Clang, javac, CPython, V8), explains why the compiler emitted it, and pairs Broken vs Fixed code so you can diff the change directly.

At a glance

The 3 things this guide delivers

Verbatim compiler output

GCC 13.2, Clang 17, MSVC 19.39, javac, CPython, V8. The exact diagnostic the toolchain prints, not a paraphrase.

Broken vs Fixed pairs

Each error ships with the snippet that triggers it and the patch that resolves it. Diff the change side by side.

Root cause every time

Not just the fix. The reason the compiler emits the message, so the next instance of the pattern looks familiar.

Section 1 of 47

How to read this guide

Each error follows the same 3-block structure. Broken reproduces the failure. Compiler output is the verbatim message the toolchain emits (different vendors print different formats for the same bug, so we show GCC and Clang side by side where they diverge). Fixed shows the patch.

Versions used for the verbatim output: GCC 13.2, Clang 17, MSVC 19.39, OpenJDK 21, CPython 3.12, Node 20 (V8 11.3). Older toolchains may format the same diagnostic slightly differently; the cause and fix do not change.

Section 2 of 47

C compile errors (10 entries)

1. expected `;` before `}` token

Cause: missing semicolon. The C grammar requires one after every statement. The compiler reports the column of the token after the missing one, which is why the message points at the closing brace.

Compiler output (GCC 13.2)

main.c:5:1: error: expected ';' before '}' token

Broken c
/* Broken */
#include <stdio.h>
int main(void) {
    int x = 5
    printf("%d\n", x);
    return 0;
}
Fixed c
/* Fixed: semicolon after the declaration */
#include <stdio.h>
int main(void) {
    int x = 5;
    printf("%d\n", x);
    return 0;
}

Section 3 of 47

C error 2: implicit declaration of function

Cause: calling a function before declaring it. The C89 standard let the compiler infer int return type; C99 and later make this an error (downgraded to a warning by default in GCC, promoted to error with -Werror or in any course that enables strict flags).

Compiler output (GCC 13.2)

main.c:4:5: warning: implicit declaration of function 'strlen' [-Wimplicit-function-declaration]

Broken c
/* Broken: missing #include <string.h> */
#include <stdio.h>
int main(void) {
    int n = strlen("hi");
    printf("%d\n", n);
    return 0;
}
Fixed c
/* Fixed: include the header that declares strlen */
#include <stdio.h>
#include <string.h>
int main(void) {
    int n = (int)strlen("hi");
    printf("%d\n", n);
    return 0;
}

Section 4 of 47

C error 3: incompatible pointer types

Cause: assigning a pointer of one type to a pointer of an incompatible type without a cast. Common after refactoring a function signature.

Compiler output (GCC 13.2)

main.c:6:11: warning: assignment to 'int *' from incompatible pointer type 'char *' [-Wincompatible-pointer-types]

Broken c
/* Broken */
#include <stdlib.h>
int main(void) {
    int *p;
    char *q = malloc(10);
    p = q;     /* incompatible pointer types */
    return 0;
}
Fixed c
/* Fixed: cast through void* or align the types */
#include <stdlib.h>
int main(void) {
    int *p = malloc(10 * sizeof(int));
    char *q = (char *)p;  /* explicit byte view */
    free(p);
    return 0;
}

Section 5 of 47

C error 4: undefined reference (linker)

Cause: the compile step succeeded but the linker cannot find the symbol. Five subcauses: missing source file in the link line, missing -l flag for a library, mismatched function signature between declaration and definition, an extern function never defined, or a static function visible only inside the file that defines it.

Compiler output (ld via GCC 13.2)

/usr/bin/ld: /tmp/cc6xK4Yt.o: in function `main': main.c:5: undefined reference to `compute' collect2: error: ld returned 1 exit status

Broken c
/* Broken: helper.c not compiled or linked */
/* main.c */
extern int compute(int);
int main(void) {
    return compute(42);
}
/* compile: gcc main.c -o app  -> undefined reference to compute */
Fixed c
/* Fixed: include the implementation in the link line */
/* main.c (unchanged) */
extern int compute(int);
int main(void) {
    return compute(42);
}
/* helper.c */
int compute(int x) { return x + 1; }
/* compile: gcc main.c helper.c -o app */

Section 6 of 47

C error 5: segmentation fault (runtime)

Cause: dereferencing an invalid pointer (NULL, freed, uninitialized) or writing past an allocated buffer. Not a compiler error; the program compiles, runs, then dies. Reproduce under Valgrind: valgrind --leak-check=full ./app reports the line that triggered the invalid access. Or under GDB: run, hit segfault, bt for backtrace.

Five canonical sources of a segfault in coursework C code:

  1. NULL dereference: malloc returned NULL (out of memory or zero-size request), the code did not check.
  2. Use after free: pointer was freed earlier; the line that uses it now touches deallocated memory.
  3. Stack buffer overflow: writing past the end of char buf[10] corrupts the return address. The crash happens on function return, not on the write itself.
  4. Heap buffer overflow: writing past malloc'd memory corrupts allocator metadata. The crash happens on the next malloc or free, far from the offending write.
  5. Stack overflow: infinite or very deep recursion exhausts the 2 MB default stack and the kernel sends SIGSEGV.

Runtime output

Segmentation fault (core dumped)

Broken c
/* Broken: dereferencing NULL */
#include <stdio.h>
int main(void) {
    int *p = NULL;
    *p = 42;       /* segfault here */
    printf("%d\n", *p);
    return 0;
}
Fixed c
/* Fixed: allocate before dereferencing */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
    int *p = malloc(sizeof(int));
    if (!p) return 1;
    *p = 42;
    printf("%d\n", *p);
    free(p);
    return 0;
}

Section 7 of 47

C error 6: stack smashing detected

Cause: writing past the end of a stack-allocated buffer triggered the canary check inserted by GCC -fstack-protector-strong. The runtime kills the process before the buffer overflow can return to attacker-controlled code.

Runtime output

*** stack smashing detected ***: terminated Aborted (core dumped)

Broken c
/* Broken: writing 20 bytes into a 10-byte buffer */
#include <string.h>
int main(void) {
    char buf[10];
    strcpy(buf, "this string is too long");
    return 0;
}
Fixed c
/* Fixed: bound the copy with strncpy or snprintf */
#include <string.h>
#include <stdio.h>
int main(void) {
    char buf[10];
    snprintf(buf, sizeof(buf), "%s", "this string is too long");
    return 0;
}

Section 8 of 47

C error 7: format specifier does not match argument type

Cause: printf format string disagrees with the argument. GCC -Wformat catches most cases at compile time. Mismatch causes garbage output at best, stack corruption at worst.

Compiler output (GCC 13.2)

main.c:5:18: warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]

Broken c
/* Broken */
#include <stdio.h>
int main(void) {
    double pi = 3.14;
    printf("pi = %d\n", pi);   /* %d expects int */
    return 0;
}
Fixed c
/* Fixed: %f for double, or cast intentionally */
#include <stdio.h>
int main(void) {
    double pi = 3.14;
    printf("pi = %f\n", pi);
    return 0;
}

Section 9 of 47

C error 8: control reaches end of non-void function

Cause: a function declared to return a value has a code path that does not. The standard says the return value is undefined; some compilers warn, others error with strict flags.

Compiler output (GCC 13.2)

main.c:7:1: warning: control reaches end of non-void function [-Wreturn-type]

Broken c
/* Broken */
int sign(int x) {
    if (x > 0) return 1;
    if (x < 0) return -1;
    /* zero case falls off the end */
}
Fixed c
/* Fixed: handle every path */
int sign(int x) {
    if (x > 0) return 1;
    if (x < 0) return -1;
    return 0;
}

Section 10 of 47

C error 9: assignment makes integer from pointer without cast

Cause: comparing or assigning NULL to an integer, or vice versa. Frequent in loops that confuse strchr (returns char *) with strcmp (returns int).

Compiler output (GCC 13.2)

main.c:6:11: warning: assignment to 'int' from 'char *' makes integer from pointer without a cast [-Wint-conversion]

Broken c
/* Broken */
#include <string.h>
int main(void) {
    char s[] = "hello";
    int found = strchr(s, 'e');  /* strchr returns char* */
    return found;
}
Fixed c
/* Fixed: store the pointer or test against NULL */
#include <string.h>
int main(void) {
    char s[] = "hello";
    char *found = strchr(s, 'e');
    return found != NULL;
}

Section 11 of 47

C error 10: dereferencing pointer to incomplete type

Cause: accessing a member of a struct whose definition the compiler has not seen. The header forward-declared the type but never defined it, or the include order is wrong.

Compiler output (GCC 13.2)

main.c:5:13: error: dereferencing pointer to incomplete type 'struct Node'

Broken c
/* Broken: forward decl without definition */
struct Node;
int main(void) {
    struct Node *n = 0;
    return n->value;  /* incomplete type */
}
Fixed c
/* Fixed: define the struct before use */
struct Node { int value; struct Node *next; };
int main(void) {
    struct Node n = {42, 0};
    return n.value;
}

Section 12 of 47

C++ compile errors (10 entries)

11. invalid conversion from `int*` to `int` (C++ stricter than C)

Cause: C++ does not allow the implicit pointer-to-int conversion that C tolerates. A pointer never becomes a number without an explicit reinterpret_cast or intptr_t conversion.

Compiler output (g++ 13.2)

main.cpp:4:9: error: invalid conversion from 'int*' to 'int' [-fpermissive]

Broken cpp
// Broken
int main() {
    int x = 5;
    int y = &x;     // pointer to int implicit conversion
    return y;
}
Fixed cpp
// Fixed: dereference the pointer, or capture address explicitly
#include <cstdint>
int main() {
    int x = 5;
    int y = x;                                    // copy the value
    std::intptr_t addr = reinterpret_cast<std::intptr_t>(&x);
    return y + (addr & 1);
}

Section 13 of 47

C++ error 12: no matching function for call to

Cause: the overload resolver could not find an exact match. Either no overload exists, or implicit conversions cannot reach any candidate without ambiguity.

Compiler output (Clang 17)

main.cpp:8:5: error: no matching function for call to 'add' note: candidate function template not viable: requires 2 arguments, but 3 were provided

Broken cpp
// Broken: calling add with wrong arity
template <typename T>
T add(T a, T b) { return a + b; }

int main() {
    return add(1, 2, 3);   // no 3-arg overload
}
Fixed cpp
// Fixed: variadic template or correct arity
#include <numeric>

template <typename... T>
auto add(T... xs) { return (xs + ...); }   // fold expression (C++17)

int main() {
    return add(1, 2, 3);   // 6
}

Section 14 of 47

C++ error 13: cannot bind non-const lvalue reference to rvalue

Cause: passing a temporary (rvalue) to a function that takes a non-const reference. Non-const references can only bind to lvalues so the function cannot mutate a temporary that disappears at the next semicolon.

Compiler output (g++ 13.2)

main.cpp:6:8: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

Broken cpp
// Broken
void inc(int& x) { x += 1; }
int main() {
    inc(5);   // 5 is a temporary
    return 0;
}
Fixed cpp
// Fixed: pass a named variable, or accept const& or by value
void inc(int& x) { x += 1; }
int main() {
    int n = 5;
    inc(n);
    return n;   // 6
}

Section 15 of 47

C++ error 14: use of deleted function (copy)

Cause: a class with a deleted copy constructor (commonly std::unique_ptr, std::thread, anything RAII-wrapping an exclusive resource) is being copied. Move it instead.

Compiler output (g++ 13.2)

main.cpp:6:24: error: use of deleted function 'std::unique_ptr...&)'

Broken cpp
// Broken: copying unique_ptr
#include <memory>
int main() {
    auto a = std::make_unique<int>(42);
    auto b = a;          // deleted copy constructor
    return *b;
}
Fixed cpp
// Fixed: move-transfer ownership
#include <memory>
#include <utility>
int main() {
    auto a = std::make_unique<int>(42);
    auto b = std::move(a);   // a is now empty
    return *b;
}

Section 16 of 47

C++ error 15: undefined reference to vtable for Foo

Cause: a class with at least one virtual function had one defined inline and at least one declared but not defined. The compiler emits the vtable in the translation unit that defines the first non-inline virtual function; if no TU defines it, the linker fails.

Compiler output (ld via g++ 13.2)

/usr/bin/ld: /tmp/main.o:(.data.rel.ro._ZTV3Foo[_ZTV3Foo]+0x10): undefined reference to `Foo::greet()' collect2: error: ld returned 1 exit status

Broken cpp
// Broken: greet declared, never defined
struct Foo {
    virtual void greet();   // declared
    virtual ~Foo() {}
};
int main() {
    Foo f;
    f.greet();
    return 0;
}
Fixed cpp
// Fixed: define every declared virtual (or pure-virtual = 0)
struct Foo {
    virtual void greet() {}   // defined inline
    virtual ~Foo() {}
};
int main() {
    Foo f;
    f.greet();
    return 0;
}

Section 17 of 47

C++ error 16: passing argument discards qualifiers

Cause: passing a const object to a function that takes a non-const reference. Symmetric to the rvalue binding error but driven by qualifiers, not value category.

Compiler output (g++ 13.2)

main.cpp:7:9: error: passing 'const std::string' as 'this' argument discards qualifiers [-fpermissive]

Broken cpp
// Broken: calling non-const member on a const object
#include <string>
int main() {
    const std::string s = "hi";
    s.clear();   // clear() is not const
    return 0;
}
Fixed cpp
// Fixed: drop const, or copy
#include <string>
int main() {
    std::string s = "hi";
    s.clear();
    return 0;
}

Section 18 of 47

C++ error 17: ambiguous overload

Cause: two candidates require different implicit conversions and neither dominates. Common when mixing int, long, double, or when overloading with both pointer and integer parameters.

Compiler output (Clang 17)

main.cpp:8:5: error: call to 'add' is ambiguous note: candidate function

Broken cpp
// Broken: ambiguous between int and double overloads on 0
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
int main() {
    return add(1, 2.0);   // 1 is int, 2.0 is double, neither dominates
}
Fixed cpp
// Fixed: force one type
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
int main() {
    return add(1.0, 2.0);   // both double
}

Section 19 of 47

C++ error 18: terminate called after throwing instance

Cause: an exception escaped main or a destructor without being caught. C++ calls std::terminate, which by default calls abort.

Three places where exceptions become terminations: escaping main with no catch on the path, throwing from a destructor during stack unwinding (a second exception during cleanup of the first), and throwing across a thread boundary without joining via std::future. Coursework that uses STL containers often hits this via vector::at, map::at, stoi, or regex_match, all of which throw on bad input.

Runtime output

terminate called after throwing an instance of 'std::out_of_range' what(): vector::_M_range_check: __n (which is 5) >= this->size() (which is 3) Aborted (core dumped)

Broken cpp
// Broken: vector::at throws on out-of-range
#include <vector>
int main() {
    std::vector<int> v = {1, 2, 3};
    return v.at(5);   // throws out_of_range
}
Fixed cpp
// Fixed: bounds-check or catch
#include <vector>
#include <stdexcept>
#include <iostream>
int main() {
    std::vector<int> v = {1, 2, 3};
    try {
        return v.at(5);
    } catch (const std::out_of_range& e) {
        std::cerr << e.what() << '\n';
        return 1;
    }
}

Section 20 of 47

C++ error 19: invalid use of incomplete type std::iostream

Cause: forward-declared a stream type with class std::ostream; but never included <iostream>. Forward declarations of standard library types are not portable; the spec lets implementations use undisclosed inheritance trees.

Compiler output (g++ 13.2)

main.cpp:5:25: error: invalid use of incomplete type 'class std::basic_ostream<char>'

Broken cpp
// Broken
namespace std { class ostream; }
void log(std::ostream& os) {
    os << "hi";    // incomplete type
}
Fixed cpp
// Fixed: include the real header
#include <iostream>
void log(std::ostream& os) {
    os << "hi";
}

Section 21 of 47

C++ error 20: data race detected by ThreadSanitizer

Cause: two threads access the same memory without synchronization, at least one of which writes. Undefined behavior per the C++ memory model. Compile with -fsanitize=thread to catch it.

Why "undefined" is harsher than "wrong": the C++ memory model says a program with a data race may produce any output. The compiler is allowed to assume races never happen and aggressively optimize based on that assumption. The same code can produce different results on the same input on different runs, on different hardware (x86 vs ARM), and at different optimization levels. The fix is always synchronization, either via std::atomic for simple counters or std::mutex for non-atomic operations on shared data.

Runtime output (TSan)

WARNING: ThreadSanitizer: data race (pid=1234) Write of size 4 at 0x000000abcdef by thread T1: ... Previous read of size 4 at 0x000000abcdef by thread T2: ...

Broken cpp
// Broken: unsynchronized shared counter
#include <thread>
int counter = 0;
void inc() { for (int i = 0; i < 100000; i++) counter++; }
int main() {
    std::thread a(inc), b(inc);
    a.join(); b.join();
    return counter;   // less than 200000, races visible under TSan
}
Fixed cpp
// Fixed: atomic counter or mutex
#include <thread>
#include <atomic>
std::atomic<int> counter{0};
void inc() { for (int i = 0; i < 100000; i++) counter.fetch_add(1); }
int main() {
    std::thread a(inc), b(inc);
    a.join(); b.join();
    return counter;   // exactly 200000
}

Section 22 of 47

Java compile errors (10 entries)

21. cannot find symbol

Cause: identifier the compiler cannot resolve. Misspelled name, missing import, or a method called on the wrong receiver.

Compiler output (javac OpenJDK 21)

Main.java:5: error: cannot find symbol Sytem.out.println("hi"); ^ symbol: variable Sytem location: class Main

Broken java
// Broken: typo in System
public class Main {
    public static void main(String[] args) {
        Sytem.out.println("hi");
    }
}
Fixed java
// Fixed: spell it correctly
public class Main {
    public static void main(String[] args) {
        System.out.println("hi");
    }
}

Section 23 of 47

Java error 22: incompatible types

Cause: assigning a value of type A to a variable of type B without a legal conversion. The compiler shows both types so the fix is usually a cast or a conversion call.

Compiler output (javac OpenJDK 21)

Main.java:4: error: incompatible types: String cannot be converted to int int n = "42"; ^

Broken java
// Broken
public class Main {
    public static void main(String[] args) {
        int n = "42";    // String to int, no implicit conv
    }
}
Fixed java
// Fixed: parse explicitly
public class Main {
    public static void main(String[] args) {
        int n = Integer.parseInt("42");
        System.out.println(n);
    }
}

Section 24 of 47

Java error 23: missing return statement

Cause: a non-void method has a control path that falls off the end without returning. The compiler proves this with definite-assignment analysis.

Compiler output (javac OpenJDK 21)

Main.java:5: error: missing return statement }

Broken java
// Broken
public class Main {
    static int sign(int x) {
        if (x > 0) return 1;
        if (x < 0) return -1;
        // zero case missing
    }
}
Fixed java
// Fixed
public class Main {
    static int sign(int x) {
        if (x > 0) return 1;
        if (x < 0) return -1;
        return 0;
    }
}

Section 25 of 47

Java error 24: NullPointerException at runtime

Cause: dereferencing a null reference. Java 14 added helpful NPEs (-XX:+ShowCodeDetailsInExceptionMessages, on by default since 15) that name the variable that was null.

Three structural sources of NPEs in coursework Java code: uninitialized field (declared with default-null initializer, never assigned before use); missing null check after lookup (map.get(key) returns null for absent keys); lazy initialization race (one thread reads the field while another initializes it). Modern Java strongly recommends Optional<T> for return types where null is a legitimate value, eliminating the null-check failure mode by making the absence explicit in the type system.

Runtime output (OpenJDK 21)

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is null at Main.main(Main.java:4)

Broken java
// Broken
public class Main {
    public static void main(String[] args) {
        String s = null;
        System.out.println(s.length());   // NPE
    }
}
Fixed java
// Fixed: null check, or Optional, or non-null default
import java.util.Objects;
public class Main {
    public static void main(String[] args) {
        String s = null;
        int len = Objects.requireNonNullElse(s, "").length();
        System.out.println(len);   // 0
    }
}

Section 26 of 47

Java error 25: ArrayIndexOutOfBoundsException

Cause: reading or writing past the end of an array. The JVM bounds-checks every array access; the cost is amortized to negligible by JIT bounds-check elimination on provably safe loops.

Runtime output (OpenJDK 21)

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 at Main.main(Main.java:5)

Broken java
// Broken
public class Main {
    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        for (int i = 0; i <= a.length; i++) {  // <=, not <
            System.out.println(a[i]);
        }
    }
}
Fixed java
// Fixed: < length, or enhanced for
public class Main {
    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        for (int x : a) System.out.println(x);
    }
}

Section 27 of 47

Java error 26: ConcurrentModificationException

Cause: structurally modifying a collection while iterating it via an enhanced for-loop. The iterator detects the modification count drift and throws.

The name is misleading. The exception fires on single-threaded modification too; it has nothing to do with multiple threads. The "concurrent" in the name refers to concurrent reads (the iterator) and writes (the modification) within one method. The 4 safe modification patterns are: use Iterator.remove() for in-place removal, use Collection.removeIf(predicate) for bulk removal, collect indices to remove and apply after the loop, or copy the collection before iterating and modify the original. Concurrent collections like CopyOnWriteArrayList and ConcurrentHashMap iterate over snapshots and never throw CME, at the cost of weaker consistency guarantees.

Runtime output (OpenJDK 21)

Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$Itr.checkForComodification(...)

Broken java
// Broken
import java.util.ArrayList;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
        for (int x : list) {
            if (x == 2) list.remove(Integer.valueOf(2));  // CME
        }
    }
}
Fixed java
// Fixed: use Iterator.remove or removeIf
import java.util.ArrayList;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
        list.removeIf(x -> x == 2);
        System.out.println(list);   // [1, 3]
    }
}

Section 28 of 47

Java error 27: ClassCastException

Cause: casting a reference to a type the runtime object is not. Common in code that downcasts Object from a generic container without pattern matching.

Runtime output (OpenJDK 21)

Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String

Broken java
// Broken
public class Main {
    public static void main(String[] args) {
        Object o = 42;
        String s = (String) o;   // ClassCastException
    }
}
Fixed java
// Fixed: pattern matching for instanceof (Java 16+)
public class Main {
    public static void main(String[] args) {
        Object o = 42;
        if (o instanceof String s) {
            System.out.println(s.length());
        } else {
            System.out.println("not a string");
        }
    }
}

Section 29 of 47

Java error 28: unreported exception (must be caught or declared)

Cause: invoking a method that declares a checked exception without either catching it or declaring it on the enclosing method. Java tracks checked exceptions in method signatures.

Compiler output (javac OpenJDK 21)

Main.java:5: error: unreported exception IOException; must be caught or declared to be thrown

Broken java
// Broken
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
    public static void main(String[] args) {
        Files.readString(Path.of("config.txt"));   // throws IOException
    }
}
Fixed java
// Fixed: declare or catch
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
    public static void main(String[] args) throws IOException {
        String s = Files.readString(Path.of("config.txt"));
        System.out.println(s);
    }
}

Section 30 of 47

Java error 29: variable might not have been initialized

Cause: a local variable is read on a path the compiler cannot prove assigns it. Definite-assignment analysis is strict; even a logically-unreachable branch counts.

Compiler output (javac OpenJDK 21)

Main.java:8: error: variable result might not have been initialized return result; ^

Broken java
// Broken
public class Main {
    static int classify(int x) {
        int result;
        if (x > 0) result = 1;
        if (x < 0) result = -1;
        // zero path leaves result unassigned
        return result;
    }
}
Fixed java
// Fixed: initialize at declaration or use else
public class Main {
    static int classify(int x) {
        int result;
        if (x > 0) result = 1;
        else if (x < 0) result = -1;
        else result = 0;
        return result;
    }
}

Section 31 of 47

Java error 30: class file has wrong version 65.0, should be 61.0

Cause: running classes compiled with a newer JDK on an older runtime. Java 21 emits class file version 65, Java 17 emits 61, Java 11 emits 55, Java 8 emits 52.

Runtime output (OpenJDK 17)

Error: LinkageError occurred while loading main class Main java.lang.UnsupportedClassVersionError: Main has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0

Broken java
// Broken: compile with Java 21, run with Java 17
// $ javac --release 21 Main.java
// $ java Main      # using a Java 17 java binary -> UnsupportedClassVersionError
Fixed java
// Fixed: target the runtime version, or upgrade the JRE
// $ javac --release 17 Main.java
// $ java Main      # works on Java 17+

Section 32 of 47

Python errors (5 entries)

31. SyntaxError: invalid syntax

Cause: Python parser rejects the construct. The caret points at the column it choked on. Common causes: missing colon, mismatched parens, walrus operator before Python 3.8, f-string nesting issues.

Runtime output (CPython 3.12)

File "main.py", line 2 if x > 0 ^ SyntaxError: expected ':'

Broken python
# Broken
def sign(x):
    if x > 0
        return 1
    return -1
Fixed python
# Fixed: colon after the if condition
def sign(x):
    if x > 0:
        return 1
    return -1

Section 33 of 47

Python error 32: IndentationError: expected an indented block

Cause: a colon-introduced block has nothing under it, or the indentation is inconsistent. Python 3 forbids mixing tabs and spaces inside the same block; set the editor to expand tabs to spaces.

Runtime output (CPython 3.12)

File "main.py", line 3 return 1 ^ IndentationError: expected an indented block after 'if' statement on line 2

Broken python
# Broken: missing body
def sign(x):
    if x > 0:
return 1
Fixed python
# Fixed
def sign(x):
    if x > 0:
        return 1
    return -1

Section 34 of 47

Python error 33: TypeError: unsupported operand type(s)

Cause: an operator was called on types that do not support it. Python only does implicit numeric conversions (int promotes to float). Strings and numbers do not combine without an explicit cast.

Runtime output (CPython 3.12)

Traceback (most recent call last): File "main.py", line 2, in <module> print("age: " + 42) TypeError: can only concatenate str (not "int") to str

Broken python
# Broken
age = 42
print("age: " + age)
Fixed python
# Fixed: f-string, or str()
age = 42
print(f"age: {age}")
print("age: " + str(age))

Section 35 of 47

Python error 34: ModuleNotFoundError

Cause: import cannot find the named module. Three subcauses: not installed in this interpreter (pip install in a different venv), typo, or wrong working directory for a local relative import.

The venv-pip skew is the most common cause and the hardest to spot. A typical setup has a system Python at /usr/bin/python3, a virtualenv Python at ./venv/bin/python, and a Homebrew Python at /opt/homebrew/bin/python3. Each has its own site-packages. Running pip install numpy uses whichever pip is first on PATH, which may not be the pip belonging to the python you run. The fix is always to use the explicit module form: python -m pip install numpy. The -m pip guarantees the install lands in the site-packages of the running interpreter.

Runtime output (CPython 3.12)

Traceback (most recent call last): File "main.py", line 1, in <module> import numpy as np ModuleNotFoundError: No module named 'numpy'

Broken python
# Broken
# $ python main.py
# main.py:
import numpy as np
print(np.array([1, 2, 3]))
Fixed python
# Fixed: install into the right interpreter
# $ python -m pip install numpy
# Then re-run. Use python -m pip to avoid 'pip' pointing
# at a different interpreter than 'python'.
import numpy as np
print(np.array([1, 2, 3]))   # [1 2 3]

Section 36 of 47

Python error 35: IndexError: list index out of range

Cause: indexing past the end of a list (or before the start with a negative index smaller than -len(list)). Slicing is more forgiving: list[100:] on a 3-element list returns [], not an error.

Runtime output (CPython 3.12)

Traceback (most recent call last): File "main.py", line 3, in <module> print(nums[5]) IndexError: list index out of range

Broken python
# Broken
nums = [1, 2, 3]
print(nums[5])
Fixed python
# Fixed: bounds-check or use .get() pattern
nums = [1, 2, 3]
i = 5
if 0 <= i < len(nums):
    print(nums[i])
else:
    print(None)

Section 37 of 47

JavaScript runtime errors (5 entries)

36. TypeError: Cannot read properties of undefined (reading 'x')

Cause: accessing a property on undefined or null. Most common cause: a function returned without an explicit return value, then the caller chained a property access.

Runtime output (Node 20)

TypeError: Cannot read properties of undefined (reading 'length') at Object.<anonymous> (/tmp/main.js:3:25)

Broken javascript
// Broken
function firstWord(s) { s.split(' ')[0]; }  // missing return
const w = firstWord("hello world");
console.log(w.length);   // TypeError
Fixed javascript
// Fixed: return, or use optional chaining
function firstWord(s) { return s.split(' ')[0]; }
const w = firstWord("hello world");
console.log(w?.length);   // 5

Section 38 of 47

JS error 37: ReferenceError: variable is not defined

Cause: referencing an identifier never declared (no var, let, or const) or hoisted past its temporal dead zone. let and const are not accessible before their declaration line, even though the binding exists from the top of the block.

Runtime output (Node 20)

ReferenceError: count is not defined at Object.<anonymous> (/tmp/main.js:2:13)

Broken javascript
// Broken: temporal dead zone
console.log(count);   // TDZ error
let count = 5;
Fixed javascript
// Fixed: declare before use
let count = 5;
console.log(count);

Section 39 of 47

JS error 38: SyntaxError: Unexpected token

Cause: the parser hit a token it cannot match to any grammar rule. Common cause: JSON.parse on a string that is not valid JSON (single quotes, trailing commas, comments).

Runtime output (Node 20)

SyntaxError: Unexpected token '\'', "{'name': 'Ada'}" is not valid JSON

Broken javascript
// Broken: single quotes not valid JSON
const raw = "{'name': 'Ada'}";
const obj = JSON.parse(raw);
Fixed javascript
// Fixed: double quotes only in JSON
const raw = '{"name": "Ada"}';
const obj = JSON.parse(raw);
console.log(obj.name);   // Ada

Section 40 of 47

JS error 39: UnhandledPromiseRejection

Cause: a promise rejected with no .catch handler. Since Node 15, the default is to crash the process. Adding process.on('unhandledRejection', ...) changes the behavior but the underlying bug remains.

The async/await trap: writing async function fetch() { throw new Error(); } and calling it without await returns a rejected promise that nobody handles. The function looks synchronous to the eye but returns a Promise. Always either await the call inside a try/catch, or attach .catch() to the returned promise. ESLint rule no-floating-promises (in @typescript-eslint) catches this at lint time; enable it on every TypeScript project.

Runtime output (Node 20)

UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch().

Broken javascript
// Broken
async function fetchData() {
    throw new Error("network down");
}
fetchData();   // no .catch, no await
Fixed javascript
// Fixed: await inside try/catch, or chain .catch
async function fetchData() {
    throw new Error("network down");
}
fetchData().catch((e) => console.error(e.message));

// Or
async function main() {
    try { await fetchData(); }
    catch (e) { console.error(e.message); }
}
main();

Section 41 of 47

JS error 40: Maximum call stack size exceeded

Cause: infinite recursion, or recursion deeper than the V8 default of roughly 11,000 frames. Increase the limit with --stack-size, but the real fix is iterative reformulation.

The exact frame count depends on frame size, not just function calls. A recursive function with many local variables blows the stack at fewer frames than a tail-shaped function with one argument. For tree traversal on a balanced tree, 11,000 frames covers trees with 2^11,000 nodes, vastly more than any practical input. For tree traversal on a pathological linked-list-shaped tree (every node has only one child), 11,000 frames covers a 11,000-node tree, and the autograder will provide a 20,000-node test case to trip this. The reliable fix is always an explicit stack with a while loop.

Runtime output (Node 20)

RangeError: Maximum call stack size exceeded at recurse (/tmp/main.js:2:21)

Broken javascript
// Broken: infinite recursion
function recurse(n) {
    return recurse(n + 1);
}
recurse(0);
Fixed javascript
// Fixed: base case
function recurse(n) {
    if (n >= 10) return n;
    return recurse(n + 1);
}
console.log(recurse(0));   // 10

// Or iterative for deep stacks
function loop(n) {
    while (n < 100000) n++;
    return n;
}

Section 42 of 47

Linker errors deep dive (the post-compile failure mode)

The compile step produces object files (.o on Linux, .obj on Windows). The link step combines them with libraries to produce an executable. Three classes of linker error dominate: undefined references, multiple definitions, and ABI mismatches.

Undefined references

The linker found a symbol declaration but no definition. Five common causes:

  • Missing object file: compiled main.c but not helper.c. Fix: add helper.c (or helper.o) to the link line.
  • Missing library flag: called sqrt without -lm. Fix: add -lm at the end of the gcc command (order matters; -l flags must follow the .o files that use them).
  • Signature mismatch: declared int compute(int), defined int compute(long). Fix: align declaration with definition.
  • Static function visibility: defined static int helper() in helper.c, called from main.c. Fix: drop static so the symbol is exported, or move the caller into helper.c.
  • C++ name mangling: declared in a C header without extern "C", defined in a C source file. Fix: wrap the C header in #ifdef __cplusplus extern "C" { #endif.

Multiple definitions

The linker found two definitions of the same symbol. Always caused by a definition in a header file that gets included into multiple translation units. Fix: move the definition to a .c file, leave only the declaration in the header. For C++ inline templates and constexpr definitions, the One Definition Rule allows multiple identical definitions across TUs; tag with inline or constexpr.

ABI mismatches

Linking C++ object files compiled with different standards or compilers produces undefined-reference errors with strange mangled names like __ZN4Foo3barEv. Fix: use a single compiler and matching -std= across the whole build. Mixing libstdc++ and libc++ binaries (GCC vs Clang on Linux) is a frequent source of this on student projects that include precompiled libraries.

c
/* Common linker fix: -lm for math */
/* main.c */
#include <math.h>
#include <stdio.h>
int main(void) {
    printf("%.4f\n", sqrt(2.0));
    return 0;
}

/* Compile fails without -lm:
 * $ gcc main.c -o app
 * /usr/bin/ld: /tmp/cc...o: undefined reference to `sqrt'
 *
 * Compile succeeds with -lm at the end:
 * $ gcc main.c -o app -lm
 */
cpp
// extern "C" wrapper for a C header included from C++
// in c_api.h
#ifdef __cplusplus
extern "C" {
#endif

int legacy_compute(int x);
void legacy_init(void);

#ifdef __cplusplus
}
#endif

// in main.cpp
#include "c_api.h"
int main() {
    legacy_init();
    return legacy_compute(42);
}

Section 43 of 47

Encoding, locale, and line-ending traps

Three environment-level failures cause "works locally, fails on grader" without any code change.

UTF-8 BOM in source files

Windows text editors sometimes prepend a UTF-8 Byte Order Mark (0xEF 0xBB 0xBF) to source files. Most modern compilers tolerate it, but older javac and gcc versions emit cryptic syntax errors at column 1 of line 1. Fix: configure the editor to save as UTF-8 without BOM, or run sed -i '1s/^\xEF\xBB\xBF//' file.c to strip it.

CRLF line endings on Unix graders

Windows uses CRLF (\r\n) line endings; Unix uses LF (\n). A Bash shebang line #!/bin/bash\r fails with "bad interpreter: no such file or directory" because the kernel reads "/bin/bash\r" as the executable path. Fix: convert with dos2unix script.sh or set git's core.autocrlf=input.

Locale-dependent number parsing

German locales use comma as the decimal separator. scanf("%f", &x) on a German system reads "3,14" but rejects "3.14". Fix: call setlocale(LC_NUMERIC, "C") at program start to force the POSIX numeric locale.

Hidden non-ASCII characters

Smart quotes from a copy-paste (“” instead of ""), Greek lambda from a math paste (λ instead of lambda), Cyrillic looking like Latin (а looks like a). The editor renders both the same; the compiler treats them differently. Fix: configure the editor to highlight non-ASCII characters in source files, or run cat -A file.c | grep -v '^[[:print:]]*\\$' to surface non-printable bytes.

Section 44 of 47

Compiler flags that catch bugs before runtime

Default compilation accepts dubious code. Adding warning flags turns ambiguous constructs into errors, catching bug classes before they reach the autograder.

C and C++ recommended flags

  • -Wall: enables the standard warnings (most missing returns, unused variables, format mismatches).
  • -Wextra: enables additional checks not in -Wall (comparison between signed and unsigned, struct padding warnings).
  • -Wpedantic: warn on extensions beyond the chosen standard.
  • -Werror: treat every warning as a hard error.
  • -fsanitize=address: AddressSanitizer instruments every memory access for OOB and use-after-free at runtime. 2x slowdown, 99% catch rate.
  • -fsanitize=undefined: UndefinedBehaviorSanitizer catches signed overflow, null deref, alignment violations at runtime.
  • -fsanitize=thread: ThreadSanitizer catches data races. Mutually exclusive with AddressSanitizer.
  • -fstack-protector-strong: stack canary on every function that allocates stack arrays.
  • -g: include DWARF debug info for GDB and core dumps.
  • -O0 for debugging (no optimization, exact line correspondence), -O2 for release.

Java recommended flags

  • -Xlint:all: enable every javac warning category.
  • -Werror: promote warnings to errors.
  • --release 17 (or your target version): enforce source and bytecode level to match runtime.
  • -XX:+ShowCodeDetailsInExceptionMessages (default since Java 15): NPE messages name the variable.

Python recommended tools

  • mypy --strict: static type checking on PEP 484 type hints.
  • ruff check: lint and auto-fix common issues.
  • python -W error: turn deprecation warnings into hard errors.
  • pytest --strict-markers: reject unrecognized pytest markers (typo protection).
bash
# Recommended C compile line for coursework
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror \
    -fsanitize=address -fsanitize=undefined \
    -fstack-protector-strong \
    -g -O0 main.c -o app

# Recommended C++ compile line
g++ -std=c++17 -Wall -Wextra -Wpedantic -Werror \
    -fsanitize=address -fsanitize=undefined \
    -g -O0 main.cpp -o app

# Java with strict warnings
javac -Xlint:all -Werror --release 17 Main.java

Section 45 of 47

Why the same code passes in debug and fails in release

Debug builds (-O0) preserve every variable, do not reorder instructions, and zero-initialize stack memory in many compilers. Release builds (-O2 or -O3) aggressively reorder, eliminate dead code, inline functions, and leave stack memory uninitialized. A bug that depended on a debug-only zero will fail in release; a bug that the compiler optimized out in debug will appear in release.

Three classes of debug-vs-release divergence

  • Uninitialized memory: a stack variable that happens to be zero in debug because the compiler emitted an initializing instruction it would not in release.
  • Undefined behavior: signed integer overflow, null deref, OOB array access. The C/C++ standards say the implementation can do anything. Optimizers assume undefined behavior never happens and aggressively prune code that would only execute in the undefined case.
  • Floating-point reordering: -ffast-math in GCC and Clang allows the optimizer to reorder fp operations under the assumption that they are associative. Real fp is not associative; the answer changes.

The standard workflow: develop in debug, but run the full test suite in release before committing. Add a CI matrix that builds both. Sanitizers (-fsanitize=address, -fsanitize=undefined) catch most undefined-behavior bugs in development before they manifest as release-only failures.

Section 46 of 47

The 3 phases of compilation and where errors live

Compilation has 3 phases. Each phase produces a different error class.

Phase 1: lexing and parsing

The compiler tokenizes the source (lexing) and matches tokens to grammar rules (parsing). Errors here are syntax errors: missing semicolons, mismatched braces, invalid operators. The output is an abstract syntax tree (AST). C and C++ compilers stop at the first syntax error in a translation unit if they cannot recover; Java and Python typically continue and report multiple.

Phase 2: semantic analysis

The compiler walks the AST checking types, name resolution, and language-level invariants. Errors here are semantic errors: undefined variable, type mismatch, missing return, ambiguous overload. The output is an annotated AST with full type information.

Phase 3: code generation and linking

The compiler emits machine code or bytecode, then the linker combines object files. Errors here are linker errors and codegen warnings: undefined references, multiple definitions, missing symbols, ABI mismatches. Java's JIT compilation happens at runtime (HotSpot, GraalVM), so what looks like a Phase 3 error in C++ surfaces at the first method call in Java.

Knowing which phase emitted an error speeds up diagnosis. A parser error is always in your source; a linker error is always in your build configuration. Skip the wrong category of fix by reading the phase tag in the error output (error: parse error vs error: undefined reference).

Section 47 of 47

How to triage a new compiler error in under 5 minutes

The 40 errors above cover roughly 80% of failed Gradescope submissions on intro CS courses. For the other 20%, the triage workflow is the same:

  1. Read the first error. Compilers cascade: error 7 is often a phantom triggered by error 1.
  2. Note the line and column. C and C++ compilers point at the token after the missing one; Java and Python point at the actual line.
  3. Note the error code. -Wint-conversion, error: cannot find symbol, TypeError. The code is searchable in the standard library reference.
  4. Reproduce with a smaller test case. Cut everything not on the failing path. The error often becomes obvious in 10 lines.
  5. If still stuck, check the build flags. -Wall -Werror turns warnings into errors; --release 17 vs --release 21 changes which features are accepted; python3 main.py vs python main.py can pick different interpreters.

For autograder failures where the code compiles locally but fails on submission, the cause is usually version skew (Java 21 features rejected by a Java 17 grader, C++17 fold expressions rejected by C++14), file structure (Gradescope expects a specific file layout the assignment spec describes), or locale and encoding (a non-ASCII identifier you cannot see in your editor). The debugging guide walks the systematic reproduction steps; the language comparison page has the autograder-compatibility matrix per language.

More Resources

Other CS Reference Material

Big-O Cheatsheet

Time and space complexity for every common data structure and algorithm. Same operation shown across Java, Python, C++, and JavaScript so you can compare directly.

Open Big-O Cheatsheet

Debugging Guide

A repeatable 5-step process for finding bugs in C, C++, Java, Python, and JavaScript. With actual GDB session output, a Valgrind trace, and a pytest reproduction template.

Open Debugging Guide

Language Comparison

Java, Python, C++, JavaScript, C, and Assembly compared across 7 axes that matter for coursework. With autograder-compatibility notes per language.

Open Language Comparison

FAQ

Common Compiler Errors, Frequently Asked Questions

Why does the compiler point at the wrong line for a missing semicolon?
C and C++ compilers report the column of the next valid token after the missing one. A missing ; at the end of line 4 shows up as an error at line 5 column 1, pointing at the } or int that starts the next statement. Always check the line above the one the error points at.
Should I fix warnings before they become errors?
Yes. Most autograders compile with -Wall -Wextra -Werror, which promotes every warning to a hard error. Warnings exist because the compiler is unsure whether the construct is correct, and the unsureness usually reveals a real bug. The CSHH tutoring team treats unfixed warnings as failed code review; clean compiles ship.
What is the difference between a syntax error and a runtime error?
Syntax errors are caught by the parser before the program runs; the source is grammatically wrong. Compile-time semantic errors (type mismatch, missing return) pass the parser but fail later compile passes. Runtime errors (segfault, NullPointerException, IndexError) only fire when execution reaches the bad line with bad data. Tests can catch runtime errors; only the compiler catches the first two classes.
How do I find which line a segfault happened on?
Compile with -g for debug symbols. Then run under GDB: gdb ./app, run, when it segfaults type bt for backtrace. The top frame shows the line. Valgrind also reports the line: valgrind --leak-check=full ./app and read the "Invalid read" or "Invalid write" stack trace.
Why does my code compile locally but fail on Gradescope?
Three common causes. Language version skew (your local g++ defaults to C++20, Gradescope is C++17). Missing files or wrong file layout (the spec wants Main.java in the root, your zip nested it in a folder). Hidden non-ASCII characters (smart quotes from a paste, BOM at the file start). Compile locally with the exact flags the assignment lists.
How do I read a deeply nested template error in C++?
Read the error from the bottom up. The bottom frame is the line in your code; everything above is template instantiation context the compiler unrolled. Modern compilers truncate after 20 levels; pass -ftemplate-backtrace-limit=0 to see the full chain. Cleaner: enable concepts (C++20) so the constraint failure is named, instead of cascading through SFINAE.
Why does my Java code throw NullPointerException with no useful message?
You are on a JVM older than Java 14. Java 14 added helpful NPEs (-XX:+ShowCodeDetailsInExceptionMessages); Java 15 enabled them by default. Upgrade the JDK, or temporarily enable the flag on Java 14. The message names the variable that was null, which removes 90% of the guesswork.
How do I disable a specific warning without turning off all warnings?
GCC and Clang: #pragma GCC diagnostic ignored "-Wunused-variable" inside the file, paired with push/pop to restore. javac: @SuppressWarnings("unchecked") on the offending declaration. Use sparingly; suppressing a warning is a confession that the code is doing something suspect but you accept the risk.
Why does Python report SyntaxError on the line after the bug?
Same reason as C: the parser only knows something is wrong when the next token does not fit the grammar. A missing colon after if x > 0 shows up as a SyntaxError on the indented line below, because that line is no longer a valid statement in the context the parser expected.
What is undefined behavior and why is it dangerous?
Undefined behavior is code the standard says the implementation can do anything with: signed integer overflow, out-of-bounds pointer dereference, returning without a value from a non-void function. The compiler is allowed to optimize as if undefined behavior never happens, so a buggy program can have its bug optimized away in release builds and crash in debug builds, or vice versa. Always compile under -fsanitize=undefined in development to catch it at runtime.

Cross-linked

Related languages and subjects

Need Common Compiler Errors Help With Your Assignment?

Cheatsheets and guides cover the general ground. For your specific brief, submit it and get expert pedagogical help within 12 hours.

Submit Assignment