Skip to content

Instantly share code, notes, and snippets.

@thunder-coding
Created May 23, 2026 12:25
Show Gist options
  • Select an option

  • Save thunder-coding/300532654670a1268563e028d1667013 to your computer and use it in GitHub Desktop.

Select an option

Save thunder-coding/300532654670a1268563e028d1667013 to your computer and use it in GitHub Desktop.

Note

Some of the tricks mentioned in this document are advanced and might not be suited for begineer level. If you are facing trouble understanding this, you might want to come back to this later after a few weeks.

Fast input test (without VS Code)

A small terminal-based workflow for compiling/running Codeforces-style solutions quickly, with local-only debug code and a couple of micro-benchmarks.


Fast build (C++)

build() {
  clang++ -std=c++20 "$1.cc" -o "$1" -g -DLOCAL
}
  • Builds X.cc into ./X
  • -DLOCAL enables #ifdef LOCAL blocks
  • -g adds debug symbols (useful for gdb/lldb)

Build + run with piped input

bar() {
  build "$1"
  cat /dev/stdin | "./$1"
}

# Usage
echo "
<program input copied from Codeforces>" | bar <solution_basename>

Notes:

  • If your file is A.cc, run: bar A
  • This form reads stdin and executes the compiled binary.

Local-only testing toggles

C++: LOCAL macro

int main() {
#ifdef LOCAL
  // This runs locally when compiled with -DLOCAL.
  // On Codeforces (no LOCAL), it compiles out.
  std::cout << "Testing code here\n";
#endif
}

Python: environment variable

import os

# Set CODEFORCES_LOCAL_TESTING=true locally to enable this block.
if os.getenv("CODEFORCES_LOCAL_TESTING") == "true":
  print("testing code here...")

Avoid hashmap for primitive types

Primitive types are pre-defined types like int, long, char, float, double.

When using map, these are compared using the std::less<> which uses the < operator which is much faster than hashing and comparing.

Hashing is computationally expensive. std::map<> often provides better search time complexity. Remember that O(K) does not involve the time of hashing. Hashing is not cache friendly as well

Often a times solutions get TLEd due to using hashmaps (std::unordered_map<>) instead of maps.

Avoid unordered_sets for primitive types

Same reasoning as for above


Know your data structures well

Some data structures have specific insertion/deletion operations which perform better in certain cases when you provide some additional information. For example when inserting elements to a sorted C++ std::set<>.

1) Plain insertion: insert(value)

#include <iostream>
#include <set>

int main() {
  std::set<int> s;
  for (int i = 0; i != 2'000'000; i++) s.insert(i);
  std::cout << "Inserted " << s.size() << " elements successfully\n";
}
  • Typical complexity: O(log n) per insert → O(n log n) total.

2) Hint insertion: insert(hint, value)

#include <iostream>
#include <set>

int main() {
  std::set<int> s;
  auto it = s.begin();
  for (int i = 0; i != 2'000'000; i++) it = s.insert(it, i);
  std::cout << "Inserted " << s.size() << " elements successfully\n";
}
  • insert(pos, value) takes a hint iterator.
  • With increasing values and reusing the returned iterator, the hint is usually correct, reducing search work.
  • If the hint is bad, it falls back toward O(log n) behavior.

Use inline functions

inline functions are functions where the functions are not actually compiled but placed alongside your code. It is a compiler optimization technique.

// In C/C++ replace every of your function from:
int myFunc(...) { /* logic */ }
// to
inline int myFunc(...) { /* logic */ }

inline functions are free-optimizations which can help you pass your code through testing in edge cases


Memory allocation is slow (practical CP habits)

new / malloc / frequent container growth are often expensive due to allocator overhead and cache misses.

Habits that help:

  • Preallocate when possible (reserve, resize, static arrays)
  • Reuse buffers across test cases
  • Avoid per-element heap allocation in hot loops (e.g., millions of std::set nodes)
  • Try to move allocations of data structures as much out of loops as possible

Examples:

#include <iostream>

int main() {
  int t;
  static int a[200'001]; // fixed allocation once
  for (std::cin >> t; t; t--) {
    int n;
    std::cin >> n;
  }
}
#include <iostream>

int main() {
  int t;
  for (std::cin >> t; t; t--) {
    int n;
    std::cin >> n;
    int a[n]; // stack VLA: non-standard in C++ (GCC extension), can blow stack
  }
}
#include <string>
#include <vector>

int main() {
  // Preallocate your strings/vectors to size that you know it won't exceed.
  // Often a times much faster than having to let the data structure grow by itself
  // When the data structure grows itself, memory is copied from old space to new space which takes O(n) time
  std::string s;
  s.reserve(1'000'000);

  std::vector<int> v;
  v.reserve(1'000'000);
}

Debug output “cheat”: use stderr

On Codeforces, only stdout is judged. Output to stderr is ignored by the checker, so it’s safe for debug logs.

C++

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

int main() {
  int n;
  cin >> n;

  cerr << "n = " << n << "\n";  // debug only (stderr)
  cout << (n * 2) << "\n";      // judged output (stdout)
}

Also works:

fprintf(stderr, "debug: i=%d\n", i);

Python

import sys

n = int(input())
print(f"debug: n={n}", file=sys.stderr)  # ignored by CF judging
print(n * 2)                              # judged output

Tip: keep debug logs on stderr, keep required answers on stdout.


Warning: macros can bite (multiple evaluation)

Macros are text substitution. If a macro uses its argument more than once, passing an expression with side effects is dangerous.

Bad macro:

#define all(x) for (auto y = x.begin(); y != x.end(); y++)

Problematic use:

int i = 0;
all(v[i++]) {
  std::cout << *y << "\n";
}

After expansion:

int i = 0;
for (auto y = v[i++].begin(); y != v[i++].end(); y++) {
  std::cout << *y << "\n";
}

v[i++] appears twice (begin() and end()), so i++ runs multiple times → mismatched iterators and likely undefined behavior.

Safer patterns:

  • Don’t pass side-effect expressions into macros.
  • Use range-for, or store the expression once:
auto& row = v[i++];
for (auto it = row.begin(); it != row.end(); ++it) {
  std::cout << *it << "\n";
}

Rule of thumb: if a macro uses its parameter more than once, side-effect arguments are a bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment