Explain Java DI, AOP, and transactions
Company: TikTok
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of Java framework internals and related competencies including dependency injection, aspect-oriented programming and proxy mechanisms, transaction management, annotation processing, bean lifecycle, classpath scanning, bean scopes, and circular dependency detection or resolution.
Constraints
- 1 <= number of discovered classes <= 10^5 in the general case, but your algorithm should be near-linear in the number of classes and dependencies
- Class names are unique
- Each dependency name is a string
- annotations may include 'component' and/or 'transactional'
- A cycle is resolvable only when every bean in that cycle has scope 'singleton' and injection_type 'setter'
Examples
Input: ([('Repo', ['component'], [], 'constructor', 'singleton', False, False), ('Service', ['component', 'transactional'], ['Repo'], 'constructor', 'singleton', True, False), ('IgnoredUtil', [], [], 'constructor', 'singleton', False, False)],)
Expected Output: {'status': 'OK', 'order': ['Repo', 'Service'], 'proxies': {'Repo': 'NONE', 'Service': 'JDK'}}
Explanation: Only Repo and Service are registered because IgnoredUtil is not a component. Service depends on Repo, so Repo is created first. Service is transactional and has an interface, so it uses a JDK proxy.
Input: ([('A', ['component'], ['B'], 'setter', 'singleton', False, False), ('B', ['component', 'transactional'], ['A'], 'setter', 'singleton', False, False), ('C', ['component'], ['A'], 'constructor', 'singleton', False, False)],)
Expected Output: {'status': 'OK', 'order': ['A', 'B', 'C'], 'proxies': {'A': 'NONE', 'B': 'CGLIB', 'C': 'NONE'}}
Explanation: A and B form a cycle, but it is allowed because both are singleton beans using setter injection. Their SCC is emitted in lexicographical order. B is transactional, has no interface, and is not final, so it uses CGLIB.
Input: ([('A', ['component'], ['B'], 'constructor', 'singleton', False, False), ('B', ['component'], ['A'], 'setter', 'singleton', False, False)],)
Expected Output: {'status': 'CIRCULAR', 'beans': ['A', 'B']}
Explanation: The cycle A <-> B is not resolvable because A uses constructor injection.
Input: ([('A', ['component'], ['B'], 'constructor', 'singleton', False, False), ('B', [], [], 'constructor', 'singleton', False, False)],)
Expected Output: {'status': 'MISSING_DEPENDENCY', 'bean': 'A', 'dependency': 'B'}
Explanation: B exists in the discovered class list but is not a component, so it is not registered as a bean. A therefore has a missing dependency.
Input: ([('FinalService', ['component', 'transactional'], [], 'constructor', 'singleton', False, True)],)
Expected Output: {'status': 'UNPROXYABLE', 'bean': 'FinalService'}
Explanation: FinalService is transactional, has no interface, and is final. It cannot be proxied with JDK proxies or CGLIB.
Input: ([],)
Expected Output: {'status': 'OK', 'order': [], 'proxies': {}}
Explanation: With no discovered classes, startup succeeds trivially with no beans.
Solution
def solution(classes):
from heapq import heappush, heappop
beans = {}
component_order = []
# Classpath scanning: only classes annotated with 'component' become beans.
for item in classes:
name, annotations, dependencies, injection_type, scope, has_interface, is_final = item
ann = {a.lower() for a in annotations}
if 'component' in ann:
beans[name] = {
'annotations': ann,
'dependencies': list(dependencies),
'injection_type': injection_type.lower(),
'scope': scope.lower(),
'has_interface': bool(has_interface),
'is_final': bool(is_final),
}
component_order.append(name)
# Failure priority 1: missing dependencies, checked in component input order.
for name in component_order:
for dep in beans[name]['dependencies']:
if dep not in beans:
return {'status': 'MISSING_DEPENDENCY', 'bean': name, 'dependency': dep}
names = list(beans.keys())
graph = {name: list(beans[name]['dependencies']) for name in names}
# Tarjan's algorithm for SCCs.
index = 0
stack = []
on_stack = set()
indices = {}
lowlink = {}
sccs = []
def strongconnect(v):
nonlocal index
indices[v] = index
lowlink[v] = index
index += 1
stack.append(v)
on_stack.add(v)
for w in graph[v]:
if w not in indices:
strongconnect(w)
lowlink[v] = min(lowlink[v], lowlink[w])
elif w in on_stack:
lowlink[v] = min(lowlink[v], indices[w])
if lowlink[v] == indices[v]:
comp = []
while True:
w = stack.pop()
on_stack.remove(w)
comp.append(w)
if w == v:
break
comp.sort()
sccs.append(comp)
for name in names:
if name not in indices:
strongconnect(name)
# Failure priority 2: unresolvable cycles.
problematic = None
for comp in sccs:
has_cycle = len(comp) > 1 or (len(comp) == 1 and comp[0] in graph[comp[0]])
if not has_cycle:
continue
resolvable = all(
beans[b]['scope'] == 'singleton' and beans[b]['injection_type'] == 'setter'
for b in comp
)
if not resolvable:
if problematic is None or tuple(comp) < tuple(problematic):
problematic = comp
if problematic is not None:
return {'status': 'CIRCULAR', 'beans': problematic}
# Failure priority 3: proxy validation and proxy type selection.
proxies = {}
for name in component_order:
bean = beans[name]
if 'transactional' in bean['annotations']:
if bean['has_interface']:
proxies[name] = 'JDK'
elif not bean['is_final']:
proxies[name] = 'CGLIB'
else:
return {'status': 'UNPROXYABLE', 'bean': name}
else:
proxies[name] = 'NONE'
# Build SCC DAG for deterministic creation order.
comp_id = {}
comp_members = []
for i, comp in enumerate(sccs):
comp_members.append(comp)
for b in comp:
comp_id[b] = i
dag = [set() for _ in range(len(sccs))]
indegree = [0] * len(sccs)
# If bean depends on dep, dep's component must come before bean's component.
for bean in names:
to_comp = comp_id[bean]
for dep in graph[bean]:
from_comp = comp_id[dep]
if from_comp != to_comp and to_comp not in dag[from_comp]:
dag[from_comp].add(to_comp)
indegree[to_comp] += 1
heap = []
for i, comp in enumerate(comp_members):
if indegree[i] == 0:
heappush(heap, (comp[0], i))
order = []
while heap:
_, cid = heappop(heap)
order.extend(comp_members[cid])
for nxt in sorted(dag[cid], key=lambda x: comp_members[x][0]):
indegree[nxt] -= 1
if indegree[nxt] == 0:
heappush(heap, (comp_members[nxt][0], nxt))
return {'status': 'OK', 'order': order, 'proxies': proxies}
Time complexity: O(V + E). Space complexity: O(V + E).
Hints
- Model beans and dependencies as a directed graph. Strongly connected components help identify cycles.
- After collapsing each strongly connected component into one node, the remaining graph is a DAG that can be topologically sorted.