Improving Backend Engineer Skills: Abstract Problem Solving and Code Abstractions
The article argues that a backend engineer’s growth hinges on mastering communication, clean coding, and architecture design by consistently abstracting problems—illustrated through Go’s ServerCodec and I/O interfaces, efficient algorithms like Trie‑based word search, and Rust’s ownership model—while recommending early Go/Rust study, reading well‑commented libraries, and hands‑on service building.
In the past two years the author has worked with many new developers at Tencent and noticed common shortcomings. Because daily work is busy, systematic sharing of experience is rare, so this article records thoughts on how to improve backend development capabilities.
What constitutes a backend engineer’s professional ability? The author narrows the discussion to three core competencies: communication ability, coding ability, and architecture design ability. These are also the criteria listed in the promotion template for senior engineers.
The essential skill is the ability to accurately abstract a problem, discuss its core points with others, and jointly solve it.
Below is an example of abstracting RPC server handling in Go. The ServerCodec interface captures the essential operations for reading requests and writing responses, leaving the concrete transport (TCP, Unix socket, pipe, etc.) to the implementation.
// A ServerCodec implements reading of RPC requests and writing of
// RPC responses for the server side of an RPC session.
// The server calls ReadRequestHeader and ReadRequestBody in pairs
// to read requests from the connection, and it calls WriteResponse to
// write a response back. The server calls Close when finished with the
// connection. ReadRequestBody may be called with a nil
// argument to force the body of the request to be read and discarded.
// See NewClient's comment for information about concurrent access.
type ServerCodec interface {
ReadRequestHeader(*Request) error
ReadRequestBody(interface{}) error
WriteResponse(*Response, interface{}) error
// Close can be called multiple times and must be idempotent.
Close() error
}This abstraction allows the same business logic to work with any stream‑like data source without caring about low‑level packet handling.
Go’s standard library provides a family of similar abstractions for I/O. The following snippet shows the Reader , Writer , Closer , Seeker , and composite interfaces.
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
type ReadWriter interface {
Reader
Writer
}
type ReadCloser interface {
Reader
Closer
}
type WriteCloser interface {
Writer
Closer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
type ReadSeeker interface {
Reader
Seeker
}
type WriteSeeker interface {
Writer
Seeker
}
type ReadWriteSeeker interface {
Reader
Writer
Seeker
}These tiny, well‑named interfaces make it easy to compose powerful I/O pipelines (e.g., bufio.NewWriter , io.LimitReader ) without adding mental overhead.
As a concrete algorithmic example, the LeetCode “Word Search II” problem can be solved efficiently with a depth‑first search combined with a Trie (prefix tree). The following Java‑style solution runs in about 15 ms.
public List
findWords(char[][] board, String[] words) {
List
res = new ArrayList<>();
TrieNode root = buildTrie(words);
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
dfs(board, i, j, root, res);
}
}
return res;
}
public void dfs(char[][] board, int i, int j, TrieNode p, List
res) {
char c = board[i][j];
if (c == '#' || p.next[c - 'a'] == null) return;
p = p.next[c - 'a'];
if (p.word != null) {
res.add(p.word);
p.word = null; // de‑duplicate
}
board[i][j] = '#';
if (i > 0) dfs(board, i - 1, j, p, res);
if (j > 0) dfs(board, i, j - 1, p, res);
if (i < board.length - 1) dfs(board, i + 1, j, p, res);
if (j < board[0].length - 1) dfs(board, i, j + 1, p, res);
board[i][j] = c;
}
public TrieNode buildTrie(String[] words) {
TrieNode root = new TrieNode();
for (String w : words) {
TrieNode p = root;
for (char c : w.toCharArray()) {
int i = c - 'a';
if (p.next[i] == null) p.next[i] = new TrieNode();
p = p.next[i];
}
p.word = w;
}
return root;
}
class TrieNode {
TrieNode[] next = new TrieNode[26];
String word;
}A more verbose alternative implementation (shown below) expands the board into a graph of fourWayNode objects and performs a custom search, but it is roughly ten times slower (≈400 ms) on the same test set.
func findWords(board [][]byte, words []string) []string {
listBoard := constructListBoard(board)
bMap := constructDict(listBoard)
resultTmpMap := make(map[string]uint8)
var found bool
for i := range words {
_, found = resultTmpMap[words[i]]
if found { continue }
if search([]byte(words[i]), bMap) {
resultTmpMap[words[i]] = 0
}
}
result := make([]string, 0, len(resultTmpMap))
for k := range resultTmpMap { result = append(result, k) }
return result
}
func constructListBoard(board [][]byte) (listBoard [][]*fourWayNode) {
lineCount := len(board)
listBoard = make([][]*fourWayNode, lineCount)
colCount := len(board[0])
for i := range listBoard {
listBoard[i] = make([]*fourWayNode, colCount)
for j := range listBoard[i] {
listBoard[i][j] = &fourWayNode{b: board[i][j], line: i, col: j}
}
}
for i := range board {
for j := range board[i] {
if i-1 >= 0 { listBoard[i][j].above = listBoard[i-1][j] }
if j+1 < colCount { listBoard[i][j].right = listBoard[i][j+1] }
if i+1 < lineCount { listBoard[i][j].below = listBoard[i+1][j] }
if j-1 >= 0 { listBoard[i][j].left = listBoard[i][j-1] }
}
}
return
}
func constructDict(listBoard [][]*fourWayNode) (result map[string][]*searchPath) {
result = make(map[string][]*searchPath)
var tmp []*searchPath
for i := range listBoard {
for j := range listBoard[i] {
tmp, _ = result[string(listBoard[i][j].b)]
result[string(listBoard[i][j].b)] = append(tmp, createPath(nil, listBoard[i][j], fmt.Sprintf("%d_%d", i, j)))
}
}
return
}
func search(word []byte, bMap map[string][]*searchPath) bool {
found := true
var tmp, startVec, nextStartVec []*searchPath
var tmpSlice string
for k := range word {
tmpSlice = string(word[:k+1])
if found {
tmp, found = bMap[tmpSlice]
if found {
if k == len(word)-1 { return true }
startVec = tmp
continue
}
}
for j := range startVec {
nextStartVec = append(nextStartVec, anyMatch(startVec[j], word[k])...)
}
if len(nextStartVec) > 0 {
bMap[tmpSlice] = nextStartVec
if k == len(word)-1 { return true }
} else { break }
startVec = nextStartVec
nextStartVec = nil
}
return false
}
// (Additional helper structs and functions omitted for brevity)The discussion then moves to backend storage design: a large hash table, red‑black tree, or B‑tree can be used to store all player IDs and quickly query which friends are playing a given game. In‑memory structures give speed, while persistence, sharding, and replication address reliability.
Rust’s ownership model is mentioned as another way to achieve safe, zero‑cost abstractions: Box<T> , Rc<T> , RefCell<T> , and Weak<T> enable fine‑grained control over heap allocation and reference counting.
Practical advice includes:
Start learning Go or Rust early, especially if you are self‑motivated.
Read well‑commented source code of Go’s standard library (e.g., net/http , encoding/json ).
Study the design of large‑scale Tencent services such as recommendation systems, CKafka, etc.
Combine theory with hands‑on practice: build small services, experiment with containers, and benchmark performance.
Continuously abstract problems, compare different solutions, and refine your own “taste” for clean design.
Finally, the author encourages readers to share their own learning plans and to engage with the community for further growth.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.