Digital Wallet Design – Machine Coding Round/ LLD Solution

Digital Wallet Design for Machine Coding Round

Before diving deep into the design for Digital Wallet, please go through this article Machine Coding Round – What is it & how to crack it. It explains in detail what machine coding is & what the machine coding round is. It also mentions in detail what you should focus on while preparing for machine coding rounds & during the interviews as well.

This article will focus on the design of a Digital Wallet system like Google Pay or Paytm. But this design will be on a much smaller scale. You can’t obviously design complex systems like Google Pay or Paytm in 2-3 hours. These applications have taken years to develop, and a few more years to mature. If you are asked Google Pay design or Paytm design in a Machine Coding round, you can use the same solution below with some minor changes.

This solution can also be used for the LLD(Low-Level Design) round. As the LLD round is very similar to the Machine Coding round, you can use the same solution with some tweaks here and there. And that should suffice.

This question is asked in some of the Flipkart machine coding rounds. And the time duration for the interview is typically 1.5-2 hours. So make sure to take the time into consideration during the course of the interview. Otherwise, you won’t be able to complete the question on time.

Requirements of Digital Wallet System

Let’s first understand the requirements and the problem statement.

Mandatory Requirements

  • You are supposed to create a digital wallet system that allows people to transfer amounts between their wallets.
  • The wallet uses its own currency known as FkRupee(F₹).
  • The account balance cannot drop below F₹ 0.00.
  • The smallest amount that can be transferred between wallets is 0.0001.
  • The user should be presented with options for each action. And the options are as follows:
    1. Create Wallet – This option should create a wallet for the user.
    2. Transfer Amount – This option should enable the transfer of funds from one account to the other.
    3. Account Statement – This option should display the account statement for the specified user.
    4. Overview – This option should display all the account numbers currently in the system. Additionally, it should show the current balances for these accounts.
    5. Exit – The system should exit.

Optional Requirements

These are the requirements that are not mandatory, but good to have. Let’s go through the optional requirements.

  1. Offer 1 – When the amount is transferred from user A to user B, F₹ 10 must be added to both the sender and receiver wallets if their balance is the same.
  2. Offer 2 – Whenever Offer 2 is selected, the top 3 customers with the highest number of transactions will get F₹ 10, F₹ 5, and F₹ 2 as rewards. If there is a tie between customers, i.e., if customers have the same number of transactions, then the customer having higher account balance should be given preference. If there is still a tie, i.e., the customers have the same balance, then the customer whose account was created first should be given preference.
  3. Add one more option called FixedDeposit <fd_amount>. And whenever the option is selected, an amount equal to <fd_amount> is parked for. If for the next 5 transactions, the account balance for that account remains above <fd_amount>, the user gets F₹ 10 as interest in their account. And if the account balance goes below <fd_amount>, then the FD should be dissolved and the user would need to give the FixedDeposit command again to start a new FD.
  4. Consider another added bonus, display the <fd_amount> and remaining transactions in the Overview and Statement command also.

Also check: How to Prepare for and Crack the Coding Interview


Approaching the Problem

During the interview, focus mainly on the mandatory requirements. Because there’s no point in fulfilling optional requirements without taking care of all the mandatory requirements first. So if time permits, you can take a look at the optional requirements. And implement some of these to give you an extra edge. But you should design such that any new changes can be incorporated with as minimal changes as possible.

Now that we have discussed the problem statement, don’t jump down to the solution directly. First, take a moment to think about how you will approach the problem. And think about:

  • Which entities or classes will you include in your design?
  • What will be the data members in these classes?
  • How many service classes do you need in your design?
  • What data structure will you use to store the data?

Better yet, try to come up with a solution of your own first. Since you’ll be able to learn better that way. And after you have a solution ready, you can go through the code below.

Breaking Down the Requirements

Now that we have all the requirements for a digital wallet design clear, we need to drill down the requirements to understand it better.

Let’s look at the mandatory requirements first.

  1. Create Wallet – We need to accept the name of the user and the amount that should be there in the wallet for the user as inputs from the user.
  2. Transfer Amount – For transfering any amount, we need the sender information, the receiver information, and the amount to be transfered. And these parameters will be taken from the user.
  3. Account Statement – We will need to accept the account number as input. And the account statement should contain the current balance and the list of transactions the user has performed to date.
  4. Overview – We need no other input as it will display the current balances for all the accounts curently in the system.
  5. Exit – Simple to implement. No other inputs needed.

Now let’s look at the optional requirements one by one.

  1. Offer 1 can be easily added by comparing the sender and receiver balance and add F₹ 10 to each wallet if the balances are same.
  2. Offer 2 can be incorporated by implementing a priority queue and having the logic for all three conditions in a Comparator.
  3. Fixed Deposit can be implemented by having an extra method to take care of the fixed deposit logic.
  4. Displaying FD amount can be implemented by make small changes in the overview and statement methods.

Also check: How to crack technical interview – Google, Amazon & more


Implementation of Digital Wallet

For this article, I’ll focus on the mandatory requirements. Since the interview lasts for 1.5-2 hours, it’s better to complete the mandatory requirements first, and if time permits focus on incorporating the optional requirements later. And you can add optional requirements to the code by making some changes as mentioned above.

I have focused on completing the core functionality and keeping it as simple as possible, so I have not used any interfaces in my design. But you are free to implement interfaces and have Impl implementations for the classes you deem fit.

Sample Output

I have done a sample run of the code to demonstrate how the code will work. And here’s an example of the sample output.

OPTIONS:
1. Create wallet
2. Transfer Amount
3. Account Statement
4. Overview
5. Exit
1
YOU SELECTED CREATE WALLET
Enter name
ABC
Enter amount
100
Account created for user ABC with account number 1

OPTIONS:
1. Create wallet
2. Transfer Amount
3. Account Statement
4. Overview
5. Exit
1
YOU SELECTED CREATE WALLET
Enter name
PQR
Enter amount
100
Account created for user PQR with account number 2

OPTIONS:
1. Create wallet
2. Transfer Amount
3. Account Statement
4. Overview
5. Exit
2
YOU SELECTED TRANSFER
Enter SENDER account number
1
Enter RECEIVER account number
2
Enter amount
50
Transfer Successful

OPTIONS:
1. Create wallet
2. Transfer Amount
3. Account Statement
4. Overview
5. Exit
3
YOU SELECTED ACCOUNT STATEMENT
Enter account num
1
Summary for account number: 1
Current Balance: 50
Your Transaction History
[Transaction [from=1, to=2, amount=50, date=Sun Feb 06 20:39:25 IST 2022]]

OPTIONS:
1. Create wallet
2. Transfer Amount
3. Account Statement
4. Overview
5. Exit
4
YOU SELECTED OVERVIEW
Balance for account number 1: 50
Balance for account number 2: 150

OPTIONS:
1. Create wallet
2. Transfer Amount
3. Account Statement
4. Overview
5. Exit

Entities/ POJOs

Let’s first look at the Entities or POJOs that are a part of my design. But we need to consider one more thing. The design should be such that any new requirement can be easily added, so we need to choose our entities accordingly. The entities in this design are:

  • User – Indicates a user or customer.
  • Account – Each customer or user will have an account that holds all the information.
  • Transaction – Will hold the data for each individual transaction.

Let’s look at this one by one.

User

package com.digitalwallet.entity;

import java.util.UUID;

public class User {
	private String id;
	private String name;

	public User(String name) {
		this.id = UUID.randomUUID().toString();
		this.name = name;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

Account

package com.digitalwallet.entity;

import java.math.BigDecimal;
import java.util.Set;
import java.util.TreeSet;

import com.digitalwallet.util.AccountNumberGenerator;

public class Account {
	private int accountNumber;
	private User user;
	private BigDecimal balance;
	private Set<Transaction> transactions;

	public Account(String name, BigDecimal amount) {
		this.accountNumber = AccountNumberGenerator.getNextAccountNumber();
		this.user = new User(name);
		this.balance = amount;
		this.transactions = new TreeSet<>((a, b) -> a.getDate().compareTo(b.getDate()));
	}

	@Override
	public String toString() {
		return "Account [accountNumber=" + accountNumber + ", name=" + this.user.getName() + ", balance=" + balance
				+ ", tranactions=" + transactions + "]";
	}

	public int getAccountNumber() {
		return accountNumber;
	}

	public void setAccountNumber(int accountNumber) {
		this.accountNumber = accountNumber;
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	public BigDecimal getBalance() {
		return balance;
	}

	public void setBalance(BigDecimal balance) {
		this.balance = balance;
	}

	public Set<Transaction> getTranactions() {
		return transactions;
	}

	public void setTranactions(TreeSet<Transaction> tranactions) {
		this.transactions = tranactions;
	}
}

Transaction

package com.digitalwallet.entity;

import java.math.BigDecimal;
import java.util.Date;

public class Transaction {
	private int from;
	private int to;
	private BigDecimal amount;
	private Date date;

	public Transaction(int from, int to, BigDecimal amount, Date date) {
		this.from = from;
		this.to = to;
		this.amount = amount;
		this.date = date;
	}

	@Override
	public String toString() {
		return "Transaction [from=" + from + ", to=" + to + ", amount=" + amount + ", date=" + date + "]";
	}

	public int getFrom() {
		return from;
	}

	public void setFrom(int from) {
		this.from = from;
	}

	public int getTo() {
		return to;
	}

	public void setTo(int to) {
		this.to = to;
	}

	public BigDecimal getAmount() {
		return amount;
	}

	public void setAmount(BigDecimal amount) {
		this.amount = amount;
	}

	public Date getDate() {
		return date;
	}

	public void setDate(Date date) {
		this.date = date;
	}
}

Utility Classes

Since I wanted the account numbers to be sequential, I have used a custom utility class to generate account numbers. And my implementation generates account numbers incrementally starting from 1. But you are free to use the UUID class provided by Java to generate account numbers. An example of UUID can be seen in the User class, where I have used it to generate user ids.

This logic for account number generation is acceptable for machine coding round. But in a real-world scenario, it might cause issues. Why? Because in a multithreaded environment, the account number generated might be incorrect. Hence, additional logic will be needed to be added to take care of concurrency.

  • AccountNumberGenerator – It will assist in generating unique & incremental account numbers for our user accounts.

Let’s look at the code for our utilities.

AccountNumberGenerator

package com.digitalwallet.util;

public class AccountNumberGenerator {
	private static int accountNumber = 1;
	
	public static int getNextAccountNumber() {
		return accountNumber++;
	}
}

DAO(Data Access Object)

We need to store the data for the accounts and transactions in some type of storage. Since this is a machine coding round, we can store all the data related to the digital wallet in an in-memory database. We can use a Dao class for the same. The Dao class in our design is:

  • WalletDao – Acts as an in-memory database for storing all digital wallet data. Which will be used to store data for users and their accounts.

Let’s take a look at the code for the Dao.

WalletDao

package com.digitalwallet.dao;

import java.util.HashMap;
import java.util.Map;

import com.digitalwallet.entity.Account;

public class WalletDao {
	private Map<Integer, Account> accountMap;
	
	public WalletDao() {
		this.accountMap = new HashMap<>();;
	}

	public Map<Integer, Account> getAccountMap() {
		return accountMap;
	}

	public void setAccountMap(Map<Integer, Account> accountMap) {
		this.accountMap = accountMap;
	}
}

Services

Now we need to define the Services that will enable us to use the entities to achieve our goal of creating a digital wallet system.

The services in our design are:

  • WalletService – This class will contain all the logic and the computations necessary for the wallet to function.

Let’s look at the code for our services.

WalletService

package com.digitalwallet.service;

import java.math.BigDecimal;
import java.util.Date;

import com.digitalwallet.dao.WalletDao;
import com.digitalwallet.entity.Account;
import com.digitalwallet.entity.Transaction;

public class WalletService {
	
	private WalletDao dao;
	
	public WalletService() {
		dao = new WalletDao();
	}

	public void createWallet(String name, BigDecimal amount) {
		Account account = new Account(name, amount);
		dao.getAccountMap().put(account.getAccountNumber(), account);
		System.out.println("Account created for user " + name + " with account number " + account.getAccountNumber());
	}

	public void transfer(int fromAccNum, int toAccNum, BigDecimal transferAmount) {
		if(!validate(fromAccNum, toAccNum, transferAmount)) {
			return;
		}
		
		Transaction tran = new Transaction(fromAccNum, toAccNum, transferAmount, new Date());
		Account fromAccount = dao.getAccountMap().get(fromAccNum);
		Account toAccount = dao.getAccountMap().get(toAccNum);
		if(fromAccount.getBalance().compareTo(transferAmount) < 0) {
			System.out.println("Insufficient Balance");
			return;
		}
		
		fromAccount.setBalance(fromAccount.getBalance().subtract(transferAmount));
		toAccount.setBalance(toAccount.getBalance().add(transferAmount));
		fromAccount.getTranactions().add(tran);
		toAccount.getTranactions().add(tran);
		System.out.println("Transfer Successful");
	}

	private boolean validate(int fromAccNum, int toAccNum, BigDecimal transferAmount) {
		if(fromAccNum == toAccNum) {
			System.out.println("Sender and Receiver cannot be same.");
			return false;
		}
		if (transferAmount.compareTo(new BigDecimal(0.0001)) < 0) {
			System.out.println("Amount too low");
			return false;
		}
		if (!dao.getAccountMap().containsKey(fromAccNum)) {
			System.out.println("Invalid Sender account number");
			return false;
		}
		if (!dao.getAccountMap().containsKey(toAccNum)) {
			System.out.println("Invalid Receiver account number");
			return false;
		}
		return true;
	}

	public void statement(int accountNum) {
		Account account = dao.getAccountMap().get(accountNum);
		if(account == null) {
			System.out.println("Invalid Account Number");
			return;
		}
		System.out.println("Summary for account number: " + accountNum);
		System.out.println("Current Balance: " + account.getBalance());
		System.out.println("Your Transaction History");
		System.out.println(account.getTranactions());
	}

	public void overview() {
		for (int accNum : dao.getAccountMap().keySet()) {
			System.out.print("Balance for account number " + accNum + ": ");
			System.out.println(dao.getAccountMap().get(accNum).getBalance());
		}
	}
}

Driver

Lastly, we will need a driver class that will have a main method. And this method will be used to start our entire application.

  • Driver – Will contain the main method. Because our application doesn’t have a UI, a class with the main method is needed.

Let’s look at our Driver class.

Driver

package com.digitalwallet.driver;

import java.math.BigDecimal;
import java.util.Scanner;

import com.digitalwallet.service.WalletService;

public class Driver {
	public static void main(String[] args) {
		WalletService wService = new WalletService();
		Scanner sc = new Scanner(System.in);
		outer: while (true) {
			System.out.println("\nOPTIONS:");
			System.out.println("1. Create wallet");
			System.out.println("2. Transfer Amount");
			System.out.println("3. Account Statement");
			System.out.println("4. Overview");
			System.out.println("5. Exit");
			switch (sc.nextInt()) {
			case 1:
				System.out.println("YOU SELECTED CREATE WALLET");
				System.out.println("Enter name");
				String name = sc.next();
				System.out.println("Enter amount");
				BigDecimal amount = sc.nextBigDecimal();
				wService.createWallet(name, amount);
				break;

			case 2:
				System.out.println("YOU SELECTED TRANSFER");
				System.out.println("Enter SENDER account number");
				int from = sc.nextInt();
				System.out.println("Enter RECEIVER account number");
				int to = sc.nextInt();
				System.out.println("Enter amount");
				BigDecimal amount1 = sc.nextBigDecimal();
				wService.transfer(from, to, amount1);
				break;

			case 3:
				System.out.println("YOU SELECTED ACCOUNT STATEMENT");
				System.out.println("Enter account num");
				wService.statement(sc.nextInt());
				break;

			case 4:
				System.out.println("YOU SELECTED OVERVIEW");
				wService.overview();
				break;
				
			case 5:
				System.out.println("APPLICATION STOPPED");
				break outer;
				
			default:
				System.out.println("YOU HAVE SELECTED INVALID OPTION. PLEASE REENTER");
				break;
			}
		}
	}
}

The entire code above can also be found on GitHub.

Summing It Up

I have tried to make the design and implementation as simple as possible so it is easy for you to understand. And I also needed to take into consideration that we have just 1.5-2 hours for designing the digital wallet system.

This is not the only solution for a digital wallet design question and it may not be the perfect solution for the problem. But we have to consider the question from an interview point of view. And take it forward from there. There can be many refinements done to that above code, but I think this solution will be enough to get you through the machine coding round or the LLD round. This solution fulfills all the mandatory requirements mentioned in the problem statement.

But make sure to go through the above code and also learn about machine coding round from the article Machine Coding Round – What is it & how to crack it. And then practice on your own by creating your own design and implementation. Because if you want to improve your chances of clearing this round, you will need to practice machine coding on your own. And practice a lot.

All the best for your interviews!!!

To keep yourself up to date with our latest content, please subscribe to our newsletter by dropping your email address here: Signup for Our Newsletter.

Please follow us on Medium.

Further Reading

10 Replies to “Digital Wallet Design – Machine Coding Round/ LLD Solution”

  1. Very simple and beautifully captured all requirement. You have nailed the machine coding basics really well.
    Small suggestion if time permits we can introduce exception class and define all exceptions there. Also if DAO can be interface with all methods defined and there can be separate implementation layer like InMemoryDaoImpl class which implements DAO this way any other storage can implement DAO, this created loose coupling b/w DAO and business logic of implementation.

    1. Thank you Aditya for your kind words and suggestions. We can surely make some additions in terms of interfaces and exception classes. But as mentioned in the article, I have tried to complete the core functionality first. The code is written from an interview point of view so I have kept it simple. Will make necessary modifications if time permits. 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *