Skip to content

Practical C++ Interview Questions

45 questions with answers. Hands-on questions where you write code, a query, a formula or concrete steps.

How to approach these questions

Talk while you work: say your approach first, write the simplest correct version, then improve it and mention edge cases.

Solved — practicalStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

Write a function to reverse a string in place without using std::reverse.

Save to ▾
Show answer
#include <string> #include <utility> void reverseInPlace(std::string& s) { if (s.empty()) return; std::size_t i = 0, j = s.size() - 1; while (i < j) { std::swap(s[i], s[j]); ++i; --j; } } // Two pointers meet in the middle: O(n) time, O(1) extra space.
02

Write an efficient function to check whether a number is prime.

Save to ▾
Show answer
bool isPrime(long long n) { if (n < 2) return false; if (n % 2 == 0) return n == 2; for (long long i = 3; i * i <= n; i += 2) { if (n % i == 0) return false; } return true; } // Only odd divisors up to sqrt(n) are checked: O(sqrt n) time.
03

Write a C++ program that counts how often each word appears in the input.

Save to ▾
Show answer
#include <iostream> #include <map> #include <string> int main() { std::map<std::string, int> freq; std::string word; while (std::cin >> word) { ++freq[word]; } for (const auto& [w, c] : freq) { std::cout << w << ' ' << c << '\n'; } } // Use std::unordered_map for O(1) average lookups if sorted output is not needed.
04

Write a swap function using references and explain why a pass-by-value version fails.

Save to ▾
Show answer
void swapByValue(int a, int b) { // works on copies, caller sees no change int t = a; a = b; b = t; } void swapByRef(int& a, int& b) { // a and b alias the caller's variables int t = a; a = b; b = t; } int x = 1, y = 2; swapByRef(x, y); // now x == 2, y == 1
05

Implement a minimal unique_ptr-like class.

Save to ▾
Show answer
template <typename T> class UniquePtr { T* ptr_ = nullptr; public: explicit UniquePtr(T* p = nullptr) : ptr_(p) {} ~UniquePtr() { delete ptr_; } UniquePtr(const UniquePtr&) = delete; UniquePtr& operator=(const UniquePtr&) = delete; UniquePtr(UniquePtr&& o) noexcept : ptr_(o.ptr_) { o.ptr_ = nullptr; } UniquePtr& operator=(UniquePtr&& o) noexcept { if (this != &o) { delete ptr_; ptr_ = o.ptr_; o.ptr_ = nullptr; } return *this; } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; } T* get() const { return ptr_; } };
06

Reverse a singly linked list using pointers.

Save to ▾
Show answer
struct Node { int val; Node* next; }; Node* reverse(Node* head) { Node* prev = nullptr; while (head) { Node* nxt = head->next; head->next = prev; prev = head; head = nxt; } return prev; } // O(n) time, O(1) extra space.
07

Find the middle node of a linked list in a single pass.

Save to ▾
Show answer
Node* middle(Node* head) { Node* slow = head; Node* fast = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } return slow; } // fast moves two steps and slow one step, so slow stops at the middle.
08

Write a function that allocates a 2D matrix safely without raw new and delete.

Save to ▾
Show answer
#include <vector> std::vector<std::vector<int>> makeMatrix(int rows, int cols) { return std::vector<std::vector<int>>(rows, std::vector<int>(cols, 0)); } // Faster single-block version: index with r * cols + c struct Matrix { int rows, cols; std::vector<int> data; Matrix(int r, int c) : rows(r), cols(c), data(r * c) {} int& at(int r, int c) { return data[r * cols + c]; } };
09

Write a Shape hierarchy with a virtual area function and use it polymorphically.

Save to ▾
Show answer
#include <iostream> #include <memory> #include <vector> struct Shape { virtual ~Shape() = default; virtual double area() const = 0; }; struct Circle : Shape { double r; explicit Circle(double r) : r(r) {} double area() const override { return 3.14159 * r * r; } }; struct Rect : Shape { double w, h; Rect(double w, double h) : w(w), h(h) {} double area() const override { return w * h; } }; int main() { std::vector<std::unique_ptr<Shape>> v; v.push_back(std::make_unique<Circle>(2)); v.push_back(std::make_unique<Rect>(3, 4)); for (const auto& s : v) std::cout << s->area() << '\n'; }
10

Overload operator+ and operator<< for a Complex number class.

Save to ▾
Show answer
#include <ostream> class Complex { double re_, im_; public: Complex(double re = 0, double im = 0) : re_(re), im_(im) {} Complex operator+(const Complex& o) const { return Complex(re_ + o.re_, im_ + o.im_); } friend std::ostream& operator<<(std::ostream& os, const Complex& c) { return os << c.re_ << (c.im_ < 0 ? '-' : '+') << (c.im_ < 0 ? -c.im_ : c.im_) << 'i'; } }; // operator<< is a friend non-member so the stream stays on the left side.
11

Implement a simple String class that follows the rule of five.

Save to ▾
Show answer
#include <cstring> #include <utility> class String { char* data_ = nullptr; std::size_t len_ = 0; public: String(const char* s = "") : len_(std::strlen(s)) { data_ = new char[len_ + 1]; std::memcpy(data_, s, len_ + 1); } ~String() { delete[] data_; } String(const String& o) : String(o.data_) {} String(String&& o) noexcept : data_(std::exchange(o.data_, nullptr)), len_(std::exchange(o.len_, 0)) {} String& operator=(String o) noexcept { // copy-and-swap handles both std::swap(data_, o.data_); std::swap(len_, o.len_); return *this; } const char* c_str() const { return data_ ? data_ : ""; } };
12

Implement a thread-safe singleton in modern C++.

Save to ▾
Show answer
class Config { public: static Config& instance() { static Config inst; // initialised once, thread-safe since C++11 return inst; } Config(const Config&) = delete; Config& operator=(const Config&) = delete; private: Config() = default; }; // The Meyers singleton relies on magic statics; no mutex or double-checked locking needed.
13

Implement a basic Observer pattern for an event publisher.

Save to ▾
Show answer
#include <functional> #include <string> #include <vector> class EventBus { public: using Handler = std::function<void(const std::string&)>; int subscribe(Handler h) { handlers_.push_back({nextId_, std::move(h)}); return nextId_++; } void unsubscribe(int id) { std::erase_if(handlers_, [id](const auto& p) { return p.first == id; }); } void publish(const std::string& msg) const { for (const auto& [id, h] : handlers_) h(msg); } private: std::vector<std::pair<int, Handler>> handlers_; int nextId_ = 0; };
14

Show the copy-and-swap idiom and explain why it gives strong exception safety.

Save to ▾
Show answer
class Buffer { std::size_t n_; int* p_; public: explicit Buffer(std::size_t n) : n_(n), p_(new int[n]{}) {} Buffer(const Buffer& o) : n_(o.n_), p_(new int[o.n_]) { std::copy(o.p_, o.p_ + n_, p_); } ~Buffer() { delete[] p_; } friend void swap(Buffer& a, Buffer& b) noexcept { std::swap(a.n_, b.n_); std::swap(a.p_, b.p_); } Buffer& operator=(Buffer other) noexcept { // copy made before we touch *this swap(*this, other); return *this; } }; // If the copy throws, *this is unchanged; self-assignment is safe too.
15

Write a generic function template that returns the larger of two values.

Save to ▾
Show answer
template <typename T> const T& maxOf(const T& a, const T& b) { return (a < b) ? b : a; } // usage int m = maxOf(3, 7); // T = int double d = maxOf(2.5, 1.5); // T = double auto s = maxOf<double>(3, 4.5); // explicit T avoids deduction conflict
16

Implement a generic Stack class template backed by std::vector.

Save to ▾
Show answer
#include <stdexcept> #include <vector> template <typename T> class Stack { std::vector<T> items_; public: void push(const T& x) { items_.push_back(x); } void push(T&& x) { items_.push_back(std::move(x)); } void pop() { if (items_.empty()) throw std::out_of_range("empty stack"); items_.pop_back(); } const T& top() const { return items_.back(); } bool empty() const { return items_.empty(); } std::size_t size() const { return items_.size(); } };
17

Write a variadic function that sums any number of arguments and one that prints them.

Save to ▾
Show answer
#include <iostream> template <typename... Args> auto sum(Args... args) { return (args + ...); // unary right fold } template <typename... Args> void printAll(const Args&... args) { ((std::cout << args << ' '), ...); // comma fold std::cout << '\n'; } // sum(1, 2, 3.5) returns 6.5; printAll(1, 'x', 2.5) prints 1 x 2.5
18

Write a C++20 concept and use it to constrain a function.

Save to ▾
Show answer
#include <concepts> #include <string> template <typename T> concept Printable = requires(T t) { { t.toString() } -> std::convertible_to<std::string>; }; template <Printable T> std::string describe(const T& obj) { return "Item: " + obj.toString(); } struct User { std::string name; std::string toString() const { return name; } }; // describe(User{"Asha"}) compiles; describe(42) fails with a clear message.
19

Implement your own is_pointer type trait.

Save to ▾
Show answer
template <typename T> struct IsPointer { static constexpr bool value = false; }; template <typename T> struct IsPointer<T*> { // partial specialization for pointers static constexpr bool value = true; }; template <typename T> inline constexpr bool IsPointer_v = IsPointer<T>::value; static_assert(IsPointer_v<int*>); static_assert(!IsPointer_v<int>); // The real std::is_pointer also strips const and volatile first.
20

Compute a factorial at compile time.

Save to ▾
Show answer
constexpr unsigned long long factorial(unsigned n) { unsigned long long r = 1; for (unsigned i = 2; i <= n; ++i) r *= i; return r; } static_assert(factorial(5) == 120); constexpr auto f10 = factorial(10); // computed by the compiler // Pre-C++14 style used template recursion: // template <unsigned N> struct Fact { static const unsigned long long v = N * Fact<N - 1>::v; }; // template <> struct Fact<0> { static const unsigned long long v = 1; };
21

Sort a vector of pairs by the second value in descending order, breaking ties by the first value.

Save to ▾
Show answer
#include <algorithm> #include <vector> std::vector<std::pair<int, int>> v = {{1, 5}, {2, 3}, {3, 5}}; std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { if (a.second != b.second) return a.second > b.second; return a.first < b.first; }); // Result: {1,5} {3,5} {2,3}. The comparator must be a strict weak ordering.
22

Solve two sum: return indices of two numbers that add up to a target.

Save to ▾
Show answer
#include <unordered_map> #include <vector> std::vector<int> twoSum(const std::vector<int>& nums, int target) { std::unordered_map<int, int> seen; // value -> index for (int i = 0; i < static_cast<int>(nums.size()); ++i) { auto it = seen.find(target - nums[i]); if (it != seen.end()) return {it->second, i}; seen[nums[i]] = i; } return {}; } // O(n) time, O(n) space.
23

Implement an LRU cache with get and put in O(1).

Save to ▾
Show answer
#include <list> #include <unordered_map> class LRUCache { int cap_; std::list<std::pair<int, int>> items_; // front = most recent std::unordered_map<int, std::list<std::pair<int, int>>::iterator> pos_; public: explicit LRUCache(int capacity) : cap_(capacity) {} int get(int key) { auto it = pos_.find(key); if (it == pos_.end()) return -1; items_.splice(items_.begin(), items_, it->second); return it->second->second; } void put(int key, int value) { if (auto it = pos_.find(key); it != pos_.end()) { it->second->second = value; items_.splice(items_.begin(), items_, it->second); return; } if (static_cast<int>(items_.size()) == cap_) { pos_.erase(items_.back().first); items_.pop_back(); } items_.emplace_front(key, value); pos_[key] = items_.begin(); } };
24

Remove duplicates from a vector of integers.

Save to ▾
Show answer
#include <algorithm> #include <vector> void dedupSorted(std::vector<int>& v) { std::sort(v.begin(), v.end()); v.erase(std::unique(v.begin(), v.end()), v.end()); } // O(n log n). std::unique only removes adjacent duplicates, so sort first. // To keep the original order, track seen values in an unordered_set instead.
25

Write a custom hash so a struct can be used as a key in std::unordered_map.

Save to ▾
Show answer
#include <functional> #include <string> #include <unordered_map> struct Point { int x, y; bool operator==(const Point&) const = default; }; struct PointHash { std::size_t operator()(const Point& p) const noexcept { std::size_t h1 = std::hash<int>{}(p.x); std::size_t h2 = std::hash<int>{}(p.y); return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); } }; std::unordered_map<Point, std::string, PointHash> grid;
26

Group a list of words into anagram groups.

Save to ▾
Show answer
#include <algorithm> #include <string> #include <unordered_map> #include <vector> std::vector<std::vector<std::string>> groupAnagrams(const std::vector<std::string>& words) { std::unordered_map<std::string, std::vector<std::string>> groups; for (const auto& w : words) { std::string key = w; std::sort(key.begin(), key.end()); groups[key].push_back(w); } std::vector<std::vector<std::string>> out; for (auto& [k, g] : groups) out.push_back(std::move(g)); return out; } // O(n * k log k) where k is the word length.
27

Use lambdas with standard algorithms to sort strings by length and count those longer than a limit.

Save to ▾
Show answer
#include <algorithm> #include <string> #include <vector> std::vector<std::string> names = {"Ravi", "Anjali", "Om", "Priyanka"}; std::size_t limit = 4; std::sort(names.begin(), names.end(), [](const std::string& a, const std::string& b) { return a.size() < b.size(); }); auto longCount = std::count_if(names.begin(), names.end(), [limit](const std::string& s) { return s.size() > limit; }); // names: Om, Ravi, Anjali, Priyanka; longCount == 2
28

Write a move constructor and move assignment operator for a class that owns a buffer.

Save to ▾
Show answer
#include <utility> class Buffer { std::size_t size_ = 0; int* data_ = nullptr; public: explicit Buffer(std::size_t n) : size_(n), data_(new int[n]{}) {} ~Buffer() { delete[] data_; } Buffer(Buffer&& other) noexcept : size_(std::exchange(other.size_, 0)), data_(std::exchange(other.data_, nullptr)) {} Buffer& operator=(Buffer&& other) noexcept { if (this != &other) { delete[] data_; size_ = std::exchange(other.size_, 0); data_ = std::exchange(other.data_, nullptr); } return *this; } };
29

Write a lookup function that uses std::optional instead of returning -1 or a null pointer.

Save to ▾
Show answer
#include <optional> #include <string> #include <unordered_map> std::optional<int> findAge(const std::unordered_map<std::string, int>& db, const std::string& name) { if (auto it = db.find(name); it != db.end()) return it->second; return std::nullopt; } // caller if (auto age = findAge(db, "Meera")) { use(*age); } int a = findAge(db, "Unknown").value_or(0);
30

Use std::variant and std::visit to handle different message types.

Save to ▾
Show answer
#include <iostream> #include <string> #include <variant> struct Login { std::string user; }; struct Logout { int sessionId; }; using Message = std::variant<Login, Logout>; template <class... Ts> struct Overloaded : Ts... { using Ts::operator()...; }; void handle(const Message& m) { std::visit(Overloaded{ [](const Login& l) { std::cout << "login " << l.user << '\n'; }, [](const Logout& o) { std::cout << "logout " << o.sessionId << '\n'; }}, m); } // Adding a new type without a handler becomes a compile error.
31

Use a C++20 ranges pipeline to get squares of the first three even numbers.

Save to ▾
Show answer
#include <iostream> #include <ranges> #include <vector> int main() { std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8}; auto result = v | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::transform([](int x) { return x * x; }) | std::views::take(3); for (int x : result) std::cout << x << ' '; // 4 16 36 } // Views are lazy: elements are processed only while iterating.
32

Show how std::format and std::print format output in modern C++.

Save to ▾
Show answer
#include <format> #include <print> // C++23 #include <string> std::string name = "Kiran"; double score = 92.456; std::string line = std::format("{} scored {:.1f}%", name, score); // C++20 std::print("{:<10}|{:>8.2f}\n", name, score); // C++23 std::string hex = std::format("{:#x}", 255); // 0xff // Type-safe like iostreams, readable like printf, and checked at compile time.
33

Write a program where two threads print odd and even numbers alternately up to 10.

Save to ▾
Show answer
#include <condition_variable> #include <iostream> #include <mutex> #include <thread> std::mutex m; std::condition_variable cv; int n = 1; const int kMax = 10; void printer(int parity) { std::unique_lock<std::mutex> lk(m); while (true) { cv.wait(lk, [&] { return n > kMax || n % 2 == parity; }); if (n > kMax) break; std::cout << n++ << '\n'; cv.notify_all(); } cv.notify_all(); } int main() { std::thread odd(printer, 1), even(printer, 0); odd.join(); even.join(); }
34

Implement a thread-safe blocking queue.

Save to ▾
Show answer
#include <condition_variable> #include <mutex> #include <optional> #include <queue> template <typename T> class BlockingQueue { std::queue<T> q_; std::mutex m_; std::condition_variable cv_; bool closed_ = false; public: void push(T v) { { std::lock_guard<std::mutex> lk(m_); q_.push(std::move(v)); } cv_.notify_one(); } std::optional<T> pop() { std::unique_lock<std::mutex> lk(m_); cv_.wait(lk, [&] { return closed_ || !q_.empty(); }); if (q_.empty()) return std::nullopt; // closed and drained T v = std::move(q_.front()); q_.pop(); return v; } void close() { { std::lock_guard<std::mutex> lk(m_); closed_ = true; } cv_.notify_all(); } };
35

Compute the sum of a large vector in parallel using std::async.

Save to ▾
Show answer
#include <future> #include <numeric> #include <vector> long long parallelSum(const std::vector<int>& v, int parts = 4) { std::vector<std::future<long long>> futs; std::size_t chunk = v.size() / parts; for (int i = 0; i < parts; ++i) { auto b = v.begin() + i * chunk; auto e = (i == parts - 1) ? v.end() : b + chunk; futs.push_back(std::async(std::launch::async, [b, e] { return std::accumulate(b, e, 0LL); })); } long long total = 0; for (auto& f : futs) total += f.get(); return total; } // C++17 alternative: std::reduce(std::execution::par, v.begin(), v.end(), 0LL)
36

Implement a simple fixed-size thread pool.

Save to ▾
Show answer
#include <condition_variable> #include <functional> #include <mutex> #include <queue> #include <thread> #include <vector> class ThreadPool { std::vector<std::thread> workers_; std::queue<std::function<void()>> tasks_; std::mutex m_; std::condition_variable cv_; bool stop_ = false; public: explicit ThreadPool(std::size_t n) { for (std::size_t i = 0; i < n; ++i) workers_.emplace_back([this] { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lk(m_); cv_.wait(lk, [this] { return stop_ || !tasks_.empty(); }); if (stop_ && tasks_.empty()) return; task = std::move(tasks_.front()); tasks_.pop(); } task(); } }); } void submit(std::function<void()> f) { { std::lock_guard<std::mutex> lk(m_); tasks_.push(std::move(f)); } cv_.notify_one(); } ~ThreadPool() { { std::lock_guard<std::mutex> lk(m_); stop_ = true; } cv_.notify_all(); for (auto& w : workers_) w.join(); } };
37

Write a thread-safe counter using std::atomic.

Save to ▾
Show answer
#include <atomic> #include <iostream> #include <thread> #include <vector> int main() { std::atomic<long> hits{0}; std::vector<std::thread> ts; for (int t = 0; t < 4; ++t) ts.emplace_back([&] { for (int i = 0; i < 1000000; ++i) hits.fetch_add(1, std::memory_order_relaxed); }); for (auto& t : ts) t.join(); std::cout << hits.load() << '\n'; // always 4000000 }
38

Write a deadlock-free money transfer between two bank accounts.

Save to ▾
Show answer
#include <mutex> #include <stdexcept> struct Account { std::mutex m; long long balance = 0; }; void transfer(Account& from, Account& to, long long amount) { if (&from == &to) return; std::scoped_lock lock(from.m, to.m); // locks both without deadlock if (from.balance < amount) throw std::runtime_error("insufficient funds"); from.balance -= amount; to.balance += amount; }
39

Write a minimal modern CMakeLists.txt for an app with a library and tests.

Save to ▾
Show answer
cmake_minimum_required(VERSION 3.20) project(Inventory LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(core src/stock.cpp src/order.cpp) target_include_directories(core PUBLIC include) target_compile_options(core PRIVATE -Wall -Wextra -Wpedantic) add_executable(app src/main.cpp) target_link_libraries(app PRIVATE core) enable_testing() find_package(GTest REQUIRED) add_executable(core_tests tests/stock_test.cpp) target_link_libraries(core_tests PRIVATE core GTest::gtest_main) add_test(NAME core_tests COMMAND core_tests)
40

List the gdb commands you would use to debug a crash.

Save to ▾
Show answer
g++ -g -O0 main.cpp -o app # build with debug symbols gdb ./app # or: gdb ./app core (gdb) run arg1 arg2 # start the program (gdb) bt # backtrace after the crash (gdb) frame 2 # select a stack frame (gdb) info locals # show local variables (gdb) print ptr->size # inspect an expression (gdb) break order.cpp:42 # set a breakpoint (gdb) watch total # stop when a variable changes (gdb) next / step / continue # step over, step into, resume
41

Measure how long a function takes to run in C++.

Save to ▾
Show answer
#include <chrono> #include <iostream> template <typename F> long long timeMs(F&& f) { auto start = std::chrono::steady_clock::now(); f(); auto end = std::chrono::steady_clock::now(); return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); } // usage // auto ms = timeMs([] { heavyWork(); }); // Use steady_clock, not system_clock, because it never jumps backwards.
42

Write a GoogleTest unit test for a function, including an edge case.

Save to ▾
Show answer
#include <gtest/gtest.h> #include <stdexcept> #include <vector> double average(const std::vector<int>& v) { if (v.empty()) throw std::invalid_argument("empty"); long long s = 0; for (int x : v) s += x; return static_cast<double>(s) / v.size(); } TEST(AverageTest, ComputesMean) { EXPECT_DOUBLE_EQ(average({2, 4, 6}), 4.0); } TEST(AverageTest, ThrowsOnEmpty) { EXPECT_THROW(average({}), std::invalid_argument); }
43

Show the compiler commands to build with strict warnings and sanitizers enabled.

Save to ▾
Show answer
# Strict warnings, treat them as errors g++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -g main.cpp -o app # Address + undefined behaviour sanitizers g++ -std=c++20 -g -O1 -fno-omit-frame-pointer \ -fsanitize=address,undefined main.cpp -o app_asan # Thread sanitizer (separate build) clang++ -std=c++20 -g -O1 -fsanitize=thread main.cpp -o app_tsan # Static analysis clang-tidy main.cpp -- -std=c++20
44

Use perf to find the hotspots in a C++ program.

Save to ▾
Show answer
# Build optimised but with symbols and frame pointers g++ -O2 -g -fno-omit-frame-pointer app.cpp -o app perf stat ./app # cycles, instructions, cache misses perf record -g ./app # sample with call graphs perf report # browse hottest functions # Flame graph (with FlameGraph scripts on PATH) perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg perf stat -e cache-misses,branch-misses ./app # specific counters
45

Write a micro-benchmark with Google Benchmark comparing two approaches.

Save to ▾
Show answer
#include <benchmark/benchmark.h> #include <list> #include <numeric> #include <vector> static void BM_VectorSum(benchmark::State& state) { std::vector<int> v(state.range(0), 1); for (auto _ : state) benchmark::DoNotOptimize(std::accumulate(v.begin(), v.end(), 0)); } BENCHMARK(BM_VectorSum)->Range(1 << 10, 1 << 20); static void BM_ListSum(benchmark::State& state) { std::list<int> l(state.range(0), 1); for (auto _ : state) benchmark::DoNotOptimize(std::accumulate(l.begin(), l.end(), 0)); } BENCHMARK(BM_ListSum)->Range(1 << 10, 1 << 20); BENCHMARK_MAIN();

Frequently asked questions

Why are practical questions important?

Most technical rounds include at least one live task. Practising the exact typing and reasoning makes you calm and fast in the real round.

How should I answer them?

Talk while you work: say your approach first, write the simplest correct version, then improve it and mention edge cases.