A dbg() that prints variable names alongside values, works on any container, and vanishes on the judge.

The macro

#ifdef LOCAL
#define dbg(...) cerr << "[" << #__VA_ARGS__ << "] = ", _print(__VA_ARGS__)
#else
#define dbg(...) 42
#endif

#__VA_ARGS__ stringifies the arguments, so dbg(x, y) prints [x, y] = 3 7. Compile locally with -DLOCAL; on the judge the macro expands to a harmless constant and costs nothing.

The printer

void _p(int x)            { cerr << x; }
void _p(long long x)      { cerr << x; }
void _p(double x)         { cerr << x; }
void _p(char x)           { cerr << '\'' << x << '\''; }
void _p(const string& x)  { cerr << '"' << x << '"'; }
void _p(bool x)           { cerr << (x ? "true" : "false"); }
 
template<class A, class B> void _p(const pair<A,B>& p);
template<class T> void _p(const T& v) {                  // any iterable
    cerr << '{';
    bool first = true;
    for (const auto& e : v) { if (!first) cerr << ", "; _p(e); first = false; }
    cerr << '}';
}
template<class A, class B> void _p(const pair<A,B>& p) {
    cerr << '(';  _p(p.first);  cerr << ", ";  _p(p.second);  cerr << ')';
}
 
void _print() { cerr << '\n'; }
template<class T, class... V> void _print(const T& t, const V&... v) {
    _p(t);
    if (sizeof...(v)) cerr << ", ";
    _print(v...);
}

The generic _p handles vector, set, map, deque, array, and nested combinations of them β€” a vector<map<int,pair<int,int>>> prints correctly with no extra code.

Using it

vector<int> a = {3, 1, 4};
map<string,int> m = {{"x", 1}};
dbg(a);              // [a] = {3, 1, 4}
dbg(m, a.size());    // [m, a.size()] = {("x", 1)}, 3

Why cerr

StreamBehaviour
coutbuffered, mixes into your answer, breaks the judge
cerrunbuffered, separate stream, ignored by the judge, redirectable with 2>

Debug output on cout is a classic self-inflicted WA. Keeping it on cerr means forgetting to remove it costs nothing β€” though it still costs time on interactive or output-heavy problems, so remove it anyway.

Compiling for debugging

g++ -std=c++20 -O2 -DLOCAL -Wall -Wextra -Wshadow -fsanitize=address,undefined -g main.cpp -o main
FlagCatches
-fsanitize=addressout-of-bounds, use-after-free, stack overflow β€” with the exact line
-fsanitize=undefinedsigned overflow, shifts width, bad casts
-D_GLIBCXX_DEBUGout-of-range vector::operator[] and bad iterators
-Wshadowa for (int i...) shadowing an outer i
-Wconversionsilent narrowing (noisy, but occasionally worth a run)

Sanitizers pay for themselves

Most runtime errors and a good share of wrong answers are out-of-bounds writes. ASan turns a mysterious WA into a stack trace with a line number, usually in under a minute. It is roughly 2x slower β€” irrelevant when you are debugging a failing case of size 8.

Tracking recursion

#ifdef LOCAL
int depth = 0;
struct Trace {
    Trace(const char* n) { cerr << string(depth++ * 2, ' ') << "-> " << n << '\n'; }
    ~Trace() { depth--; }
};
#define trace(n) Trace _t(n)
#else
#define trace(n)
#endif

An indented call tree is the fastest way to see a recursion that never terminates or that re-explores a state you thought was memoised.

What to reach for instead

For anything longer than a few minutes, a real debugger (gdb, or the IDE’s) beats print statements β€” a watchpoint on the variable that goes wrong finds the write directly. Print debugging wins on speed of setup, not on power.

See also: Debugging Techniques Β· Stress Testing Β· Contest Template