I think most Software Engineers who have gone through the interview process know the dance, but I want to talk more about the refactor “challenge” interview that I had.
For those who are new to the whole process (I cannot emphasize enough that this is not a law, it depends on the position you’re applying for, the company, experience, etc.), you usually start with a simple call where they ask about yourself, about you as an engineer, your goals, what you’re looking for, etc.
After this call is done and it looks like the position may work for you and you are the person the company is looking for, you move to the next stage which is the “technical interview.” This usually comes in two flavors, which I call sugar or salt, but wait - there may be a sugar with salt flavor, yes a third one, but this comes after.
Sugar: I call it like this because it’s very laid back - I wouldn’t call it “easy,” just more “friendly” to approach. You basically take “homework” - the company gives you a challenge to solve at “home” and you have a timeline. Although this time limit is usually more than enough to check your notes, check Google, Stack Overflow, and I guess now even ask an AI (maybe this last reason is why this flavor has been going away for the past few years).
After you’re done, you submit and schedule a new call to review your code and explain. They ask you questions about the reasoning behind your decisions and your code. Keep in mind that this “second call” is still part of the same “second stage,” meaning this is like your 2.2 call. This does not count as passing to a “next” stage or “passing a filter” - this is the same second call but in two parts: 2.1 is giving you the challenge, explaining the scope and time limit, and 2.2 is your delivery and review. Hope that makes sense.
Salt: This is the classic live coding challenge, and usually in this one I have faced Algorithms and Data Structure questions. This can be as straightforward as a LeetCode, HackerRank, etc. question, meaning they ask you to create a Binary Tree, a Graph, or any other type of data structure to solve a problem. If not that, they will ask you to do a merge sort or traverse an already given tree to find a value and maybe how you would order the thing if something changes. Fun, huh?
Salt and Sugar: This is the one that I find most fun because it’s not only about your knowledge as an engineer or the language/stack but it allows you to show both, and if you’re super strong in one area, you can always leverage your strength in the other.
In this type of question, there is the need to check that you can do some kind of mini project, maybe use models, check if you understand OOP or POP (this one mainly if you’re an iOS Developer like me), but maybe on top of that there is some kind of algorithm or data structure like a matrix, binary tree, linked list, or something that helps this mini project.
You can find an example of what I’m talking about in another post: (Interview Challenge) - Binary to Zero
If you’re not familiar with these types of questions, I can suggest you check the previously mentioned sites like LeetCode and/or HackerRank. But if you use Swift like me as your main language for coding, I can suggest: Data Structures & Algorithms in Swift (Implementing practical data structures with Swift) - Raywenderlich Team, Kelvin Lau, Vincent Ngo -
I think now they are called Kodeco.
Why am I telling you all of this? Well, because I find that even though there is information about what comes after, sometimes there are gaps. I don’t want to completely fill this gap because there are a lot of details, details that I’m not super familiar with and they vary so much that I cannot position myself as someone who can say exactly what’s going on here. Also, this third part changes again depending on the company and position, so it’s tricky, but I can help by giving you an idea of what you may encounter.
Third with Four: This “last” part comes after you have passed the “technical interview” and in some cases can be three but usually is a four-part set of calls.
Each call can be like this:
3.1 - Call with another Engineer that asks you about system design. Basically, this one is for you to layout (usually with diagrams, imagine using drawio the process from start to finish for a challenge. It can be connecting server to an app or how the information flows inside the app from a view to a class and back and what’s in the middle.
3.2 - Call with another Engineer or Designer to check your capabilities with UI/UX. Are you keen on design? How about laying something out as a proof of concept? Can you make an MVP of a View?
3.3 - Call with another Engineer to refactor/fix code. This is specifically the one I wanted to share more (in code) because I found it fun and interesting to do some retrospective. Basically, this consists of fixing/refactoring given code to be either more efficient or make it work and give your reasons why. Please check my following section for more details.
3.4 - Final call with Tech Lead and/or Manager. This one depends on your position, but if you’re applying for a Senior position (like me), you need to prove how you work with others, how you solve problems inside projects and with other teams, how you organize milestones, create scopes, mentor other developers, make code reviews, etc. If you’re not senior, this focuses more on situational problems you have faced and how you solved them (check the STAR Method)
Whew, that’s a lot, right? I’m with you, but this is the reality of the current climate. But you can understand the reasoning, right? Usually for a position with a certain level of responsibility, you need to prove several things. Well, after all of this, you should get your answer, and because you’re awesome and know it, and because you aced the questions, you get your offer (and then there’s another process which is negotiations, but I just don’t have the bandwidth to write more for now, maybe in another post).
Hopefully, this can help someone who has no idea of the general process, and if you never heard about certain topics and this helped you in some way, let me know!
Inteview Challenge, Refactor.
This is a similar question/challenge I faced on call 3.3, and after some thinking, I realized that I wish I had more time to work on my delivery.
Problem
/*
Given the following function,
implement a solution that is able
to concurrently fetch the user data
of an array of users
*/
import Foundation
let users = ["1", "2", "3", "4"]
protocol AppUser {
init(id: String, name: String, email: String)
}
// Pseudo-code for fetching user data
func async fetchUserData(for userId: String, completion: @escaping (Result<User, Error>) -> Void) thow {
// Simulate a network delay
awiit DispatchQueue.global().asyncAfter(deadline: .now() + .random(in: 0.2...1.0)) {
// Return either success with mock user data or an error
let mockData = AppUser(id: userId, name: "User \(userId)", email: "\(userId)@test.com")
completion(.success(mockData))
}
}
Solving the problem (not finished in time)
I delivered mostly the idea but the code was not running correctly
import Foundation
struct User: AppUser {
let id:String
let name:String
let email:String
}
///Wrapper
func fetchUsersData(forUsers:[String], completion: @escaping (Result<[User], Error>) ->Void){
// Check for empty
if forUsers.isEmpty {
completion(.success([]));
return
}
// Counter
var counter = 0
// Holder
var result = [AppUser]()
// Internal to check if we are done with the loop
func checkAndDeliver(newResult:AppUser?){
//Check for optional
if let user = newResult {
result.append(user)
}
counter += 1
if counter == forUsers.count-1{
completion(.success(result))
}
}
// Loop and call the singular
for user in forUsers {
fetchUserData(for:user) { result in
switch result {
case .success(let user):
checkAndDeliver(newUser:user)
break
case .failure(let error):
checkAndDeliver(newUser:nil)
break
}
}
}
}
Cleaning my mess.
After the call, I left thinking about making my code more structured, taking an async/await approach, and that I wanted to create this post to not just leave my ideas in my head but instead put them somewhere.
import Foundation
// MARK: - Models
/// Protocol defining the required properties for a user
protocol AppUser {
var id: String { get }
var name: String { get }
var email: String { get }
init(id: String, name: String, email: String)
}
/// Concrete implementation of AppUser
struct User: AppUser {
let id: String
let name: String
let email: String
}
// MARK: - Errors
enum UserFetchError: Error {
case invalidUserId
case networkError
case unknown
}
// MARK: - Original Implementation (Fixed)
/// Fetches user data for a single user
/// - Parameters:
/// - userId: The unique identifier of the user
/// - completion: Closure called with the result of the fetch operation
func fetchUserData(for userId: String, completion: @escaping (Result<User, Error>) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + .random(in: 0.2...1.0)) {
let mockData = User(id: userId, name: "User \(userId)", email: "\(userId)@test.com")
completion(.success(mockData))
}
}
// MARK: - Completion Handler Implementation
/// Fetches user data for multiple users concurrently using completion handlers
/// - Parameters:
/// - users: Array of user IDs to fetch
/// - completion: Closure called with the result of all fetch operations
func fetchUsersData(forUsers users: [String], completion: @escaping (Result<[User], Error>) -> Void) {
// Handle empty input
guard !users.isEmpty else {
completion(.success([]))
return
}
let dispatchGroup = DispatchGroup()
var results: [User] = []
let queue = DispatchQueue(label: "com.userFetch.queue", attributes: .concurrent)
for userId in users {
dispatchGroup.enter()
fetchUserData(for: userId) { result in
switch result {
case .success(let user):
queue.async(flags: .barrier) {
results.append(user)
dispatchGroup.leave()
}
case .failure(let error):
queue.async(flags: .barrier) {
dispatchGroup.leave()
}
print("Error fetching user \(userId): \(error)")
}
}
}
dispatchGroup.notify(queue: .main) {
completion(.success(results))
}
}
Extra Mile
Async/Await and some testing.
// MARK: - Modern Async/Await Implementation
/// Fetches user data for a single user using async/await
/// - Parameter userId: The unique identifier of the user
/// - Returns: A User object containing the fetched data
/// - Throws: UserFetchError if the fetch operation fails
@available(iOS 13.0, macOS 10.15, *)
func fetchUserDataAsync(for userId: String) async throws -> User {
try await Task.sleep(nanoseconds: UInt64(.random(in: 0.2...1.0) * 1_000_000_000))
return User(id: userId, name: "User \(userId)", email: "\(userId)@test.com")
}
/// Fetches user data for multiple users concurrently using async/await
/// - Parameter users: Array of user IDs to fetch
/// - Returns: Array of User objects
/// - Throws: UserFetchError if any fetch operation fails
@available(iOS 13.0, macOS 10.15, *)
func fetchUsersDataAsync(forUsers users: [String]) async throws -> [User] {
guard !users.isEmpty else { return [] }
async let fetchResults = withThrowingTaskGroup(of: User.self) { group in
for userId in users {
group.addTask {
try await fetchUserDataAsync(for: userId)
}
}
var results: [User] = []
for try await user in group {
results.append(user)
}
return results
}
return try await fetchResults
}
// MARK: - Tests
/// Simple test function to demonstrate usage
func runTests() async {
let testUsers = ["1", "2", "3"]
// Test completion handler version
print("Testing completion handler version...")
fetchUsersData(forUsers: testUsers) { result in
switch result {
case .success(let users):
print("Successfully fetched \(users.count) users")
users.forEach { print($0) }
case .failure(let error):
print("Error: \(error)")
}
}
// Test async/await version
if #available(iOS 13.0, macOS 10.15, *) {
print("\nTesting async/await version...")
do {
let users = try await fetchUsersDataAsync(forUsers: testUsers)
print("Successfully fetched \(users.count) users")
users.forEach { print($0) }
} catch {
print("Error: \(error)")
}
}
}
This was a long one, but I really wanted to lay down some information, ideas, and share my approach to a challenge that I really wish I had dealt with better. Thanks for reading.
Subscribe to My Newsletter
Get updates about coding, Swift development, and occasionally some dog stories!