Skip to main content
Powerful debug utilities inspired by tourist’s (Gennady Korotkevich) library for printing and inspecting complex data structures during competitive programming.

Overview

Effective debugging is crucial in competitive programming. These utilities allow you to easily print and inspect:
  • Basic types (int, char, string, bool)
  • STL containers (vector, set, map, queue, stack)
  • Pairs and tuples
  • Custom data structures
  • Nested containers

Basic Setup

The debug macros are enabled only during local development and disabled during online judge submission.
#ifndef ONLINE_JUDGE
#define debug(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#define log(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#define log(...) 42
#endif
Key Features:
  • Automatically prints variable names
  • Formats output for readability
  • Zero overhead in submission (compiles to nothing)
  • Works with nested structures

Core Implementation

Output Stream

#define writer_out cerr

void debug_out() { 
    writer_out << "\n"; 
}

template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
    writer_out << to_string(H);
    if(sizeof...(T)) writer_out << "] ["; 
    else writer_out << "]";
    debug_out(T...);
}

String Conversions

Basic Types

string to_string(char c) {
    return "'" + string(1, c) + "'";
}

string to_string(string s) {
    return '"' + s + '"';
}

string to_string(bool b) {
    return (b ? "true" : "false");
}

Pairs

template <typename A, typename B>
string to_string(const pair<A, B> p) {
    return "(" + to_string(p.first) + ", " + to_string(p.second) + ")";
}

Tuples

template <typename A, typename B, typename C>
string to_string(const tuple<A, B, C> p) {
    return "(" + to_string(get<0>(p)) + ", " + 
           to_string(get<1>(p)) + ", " + 
           to_string(get<2>(p)) + ")";
}

template <typename A, typename B, typename C, typename D>
string to_string(const tuple<A, B, C, D> p) {
    return "(" + to_string(get<0>(p)) + ", " + 
           to_string(get<1>(p)) + ", " + 
           to_string(get<2>(p)) + ", " + 
           to_string(get<3>(p)) + ")";
}

Container Support

Vector and Lists

template <typename A>
string to_string(A v) {
    bool first = true;
    string res = "{";
    for (auto &x : v) {
        if (!first) {
            res += ", ";
        }
        first = false;
        res += to_string(x);
    }
    res += "}";
    return res;
}
This works for: vector, set, multiset, unordered_set, list, deque

Vector of Booleans

string to_string(vector<bool> v) {
    bool first = true;
    string res = "{";
    for (int i = 0; i < (int) v.size(); i++) {
        if (!first) {
            res += ", ";
        }
        first = false;
        res += to_string(v[i]);
    }
    res += "}";
    return res;
}

Queue

template <typename T>
string to_string(queue<T> q) {
    bool first = true;
    string res = "{";
    while(!q.empty()) {
        if (!first) {
            res += ", ";
        }
        first = false;
        res += to_string(q.front());
        q.pop();
    }
    res += "}";
    return res;
}
The queue is passed by value and consumed during printing. The original queue remains unchanged.

Stack

template <typename T>
string to_string(stack<T> st) {
    bool first = true;
    string res = "{";
    while(!st.empty()) {
        if (!first) {
            res += ", ";
        }
        first = false;
        res += to_string(st.top());
        st.pop();
    }
    res += "}";
    return res;
}

Priority Queue

template<typename T, typename Sequence=vector<T>, typename Compare=less<T>>
string to_string(priority_queue<T, Sequence, Compare> pq) {
    bool first = true;
    string res = "{";
    while(!pq.empty()) {
        if (!first) {
            res += ", ";
        }
        first = false;
        res += to_string(pq.top());
        pq.pop();
    }
    res += "}";
    return res;
}

Bitset

template <size_t N>
string to_string(const bitset<N> v) {
    string res = "";
    for (size_t i = 0; i < N; i++) {
        res += static_cast<char>('0' + v[i]);
    }
    return res;
}

Usage Examples

Basic Types

int x = 42;
string name = "Alice";
char grade = 'A';
bool passed = true;

debug(x);           // [x]: [42]
debug(name);        // [name]: ["Alice"]
debug(grade);       // [grade]: ['A']
debug(passed);      // [passed]: [true]

Multiple Variables

int a = 10, b = 20, c = 30;
debug(a, b, c);     // [a, b, c]: [10] [20] [30]

string s1 = "hello", s2 = "world";
debug(s1, s2);      // [s1, s2]: ["hello"] ["world"]

Vectors

vector<int> v = {1, 2, 3, 4, 5};
debug(v);           // [v]: [{1, 2, 3, 4, 5}]

vector<string> words = {"hello", "world"};
debug(words);       // [words]: [{"hello", "world"}]

vector<bool> flags = {true, false, true};
debug(flags);       // [flags]: [{true, false, true}]

Pairs and Tuples

pair<int, int> p = {3, 5};
debug(p);           // [p]: [(3, 5)]

pair<string, int> entry = {"score", 100};
debug(entry);       // [entry]: [("score", 100)]

tuple<int, int, int> t = {1, 2, 3};
debug(t);           // [t]: [(1, 2, 3)]

Nested Structures

vector<vector<int>> matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
debug(matrix);      
// [matrix]: [{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}]

vector<pair<int, int>> edges = {{1, 2}, {2, 3}, {3, 4}};
debug(edges);       
// [edges]: [{(1, 2), (2, 3), (3, 4)}]

map<string, vector<int>> data = {
    {"alice", {90, 85, 88}},
    {"bob", {78, 82, 91}}
};
debug(data);
// [data]: [{("alice", {90, 85, 88}), ("bob", {78, 82, 91})}]

Sets and Maps

set<int> s = {3, 1, 4, 1, 5, 9};
debug(s);           // [s]: [{1, 3, 4, 5, 9}]

map<int, string> m = {{1, "one"}, {2, "two"}, {3, "three"}};
debug(m);           
// [m]: [{(1, "one"), (2, "two"), (3, "three")}]

multiset<int> ms = {1, 1, 2, 2, 2, 3};
debug(ms);          // [ms]: [{1, 1, 2, 2, 2, 3}]

Queues and Stacks

queue<int> q;
q.push(1); q.push(2); q.push(3);
debug(q);           // [q]: [{1, 2, 3}]

stack<int> st;
st.push(1); st.push(2); st.push(3);
debug(st);          // [st]: [{3, 2, 1}] (top to bottom)

priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
debug(pq);          // [pq]: [{4, 3, 1}] (max heap)

Bitsets

bitset<8> bits("10110010");
debug(bits);        // [bits]: [10110010]

bitset<16> mask = 0b1010101010101010;
debug(mask);        // [mask]: [1010101010101010]

Practical Debugging Scenarios

Debugging Loops

vector<int> arr = {5, 2, 8, 1, 9};
for(int i = 0; i < arr.size(); i++) {
    debug(i, arr[i]);
    // [i, arr[i]]: [0] [5]
    // [i, arr[i]]: [1] [2]
    // [i, arr[i]]: [2] [8]
    // ...
}

Debugging Algorithms

int binarySearch(vector<int>& arr, int target) {
    int left = 0, right = arr.size() - 1;
    
    while(left <= right) {
        int mid = left + (right - left) / 2;
        debug(left, mid, right, arr[mid]);
        
        if(arr[mid] == target) {
            return mid;
        } else if(arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return -1;
}

Debugging Graph Algorithms

vector<vector<int>> adj = {
    {1, 2},
    {0, 2, 3},
    {0, 1},
    {1}
};

vector<bool> visited(4, false);

void dfs(int node) {
    visited[node] = true;
    debug(node, visited);
    
    for(int neighbor : adj[node]) {
        if(!visited[neighbor]) {
            dfs(neighbor);
        }
    }
}

Debugging Dynamic Programming

int fibonacci(int n) {
    vector<int> dp(n + 1);
    dp[0] = 0;
    dp[1] = 1;
    
    for(int i = 2; i <= n; i++) {
        dp[i] = dp[i-1] + dp[i-2];
        debug(i, dp[i], dp);
    }
    
    return dp[n];
}

Advanced Techniques

Custom Data Structures

Define to_string for your custom types:
struct Point {
    int x, y;
};

string to_string(Point p) {
    return "Point(" + to_string(p.x) + ", " + to_string(p.y) + ")";
}

// Usage
Point p = {3, 5};
debug(p);           // [p]: [Point(3, 5)]

vector<Point> points = {{1,2}, {3,4}, {5,6}};
debug(points);      // [points]: [{Point(1, 2), Point(3, 4), Point(5, 6)}]

Conditional Debugging

#ifndef ONLINE_JUDGE
#define debug(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#define debug_if(cond, ...) if(cond) debug(__VA_ARGS__)
#else
#define debug(...) 42
#define debug_if(cond, ...) 42
#endif

// Usage
for(int i = 0; i < 100; i++) {
    debug_if(i % 10 == 0, i);  // Only print every 10th iteration
}

Function Entry/Exit Tracing

#ifndef ONLINE_JUDGE
#define TRACE_FUNC debug("Entering", __FUNCTION__)
#else
#define TRACE_FUNC
#endif

int solve(int n) {
    TRACE_FUNC;  // ["Entering", __FUNCTION__]: ["Entering"] ["solve"]
    // ... function logic
    return result;
}

Complete Template

#include <bits/stdc++.h>
using namespace std;

// ============ Debug Utilities ============
string to_string(char c) {
    return "'" + string(1, c) + "'";
}

string to_string(string s) {
    return '"' + s + '"';
}

string to_string(bool b) {
    return (b ? "true" : "false");
}

template <typename A, typename B>
string to_string(const pair<A, B> p) {
    return "(" + to_string(p.first) + ", " + to_string(p.second) + ")";
}

template <typename A>
string to_string(A v) {
    bool first = true;
    string res = "{";
    for (auto &x : v) {
        if (!first) res += ", ";
        first = false;
        res += to_string(x);
    }
    res += "}";
    return res;
}

#define writer_out cerr
void debug_out() { writer_out << "\n"; }

template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
    writer_out << to_string(H);
    if(sizeof...(T)) writer_out << "] ["; 
    else writer_out << "]";
    debug_out(T...);
}

#ifndef ONLINE_JUDGE
#define debug(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif
// =========================================

int main() {
    // Your code here
    vector<int> v = {1, 2, 3, 4, 5};
    debug(v);
    
    return 0;
}

Best Practices

Always use debug macros: They’re automatically disabled during submission, so use them liberally during development.
Debug at key points: Add debug statements before and after critical operations to track state changes.
Use meaningful variable names: The macro prints variable names, so descriptive names make debugging easier.
Don’t debug inside tight loops: Printing too much output can slow down execution significantly. Use conditional debugging instead.
Remember to remove excessive debug statements: While they’re disabled in submission, too many can make local testing slow.

Alternative Debug Approaches

Using DEBUG Flag

#ifdef DEBUG
#define debug(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif

// Compile with: g++ -DDEBUG solution.cpp

Separate Debug Header

#ifdef DEBUG
#include "debug.h"
#else
#define debug(...) 42
#endif
Create debug.h with all debug utilities, and only include it during local development.

Build docs developers (and LLMs) love