- Fibonacci Sequence
public class Fibonacci {
public static void main(String[] args) {
int n = 10; // number of terms in the sequence
int a = 0, b = 1, c;
System.out.print("Fibonacci sequence of " + n + " terms: ");
for (int i = 1; i <= n; i++) {
System.out.print(a + " ");
c = a + b;
a = b;
b = c;
}
}
}
- Generation of prime numbers
public class PrimeNumbers {
public static void main(String[] args) {
for(int i = 2; i < 100; i++){
if(isPrime(i)){
System.out.println(i);
}
}
}
static boolean isPrime(int number) {
boolean isPrime = true;
for (int i = 2; i < number; i++){
if (number % i == 0) {
isPrime = false;
break;
}
}
return isPrime;
}
}
- Stack and Heap Structure
- Authentication vs. Authorization
-
Authentication (AuthN) is a process that verifies that someone or something is who they say they are. Technology systems typically use some form of authentication to secure access to an application or its data. For example, when you need to access an online site or service, you usually have to enter your username and password. Then, behind the scenes, it compares the username and password you entered with a record it has on its database. If the information you submitted matches, the system assumes you are a valid user and grants you access. System authentication in this example presumes that only you would know the correct username and password. It, therefore, authenticates you by using the principle of something only you would know.
-
Authorization is the security process that determines a user or service's level of access. In technology, we use authorization to give users or services permission to access some data or perform a particular action. If we revisit our coffee shop example, Rahul and Lucia have different roles in the coffee shop. As Rahul is a barista, he may only place and view orders. Lucia, on the other hand, in her role as manager, may also have access to the daily sales totals. Since Rahul and Lucia have different jobs in the coffee shop, the system would use their verified identity to provide each user with individual permissions. It is vital to note the difference here between authentication and authorization. Authentication verifies the user (Lucia) before allowing them access, and authorization determines what they can do once the system has granted them access (view sales information).
- Stateless vs Stateful
- Stateless - There's no memory (state) that's maintained by the program
- Stateful - The program has a memory (state)
Stateless:
//The state is derived by what is passed into the function
function int addOne(int number)
{
return number + 1;
}
Stateful
// The state is maintained by the function
private int _number = 0; //initially zero
function int addOne()
{
_number++;
return _number;
- Deadlock
A deadlock occurs when two or more processes or threads are blocked waiting for each other to release resources that they need in order to proceed. In other words, each process or thread is holding a resource that the other process or thread needs, and neither of them can proceed until they are able to acquire the resource that the other is holding. Deadlocks can occur in a variety of situations, but they typically arise in systems where there are multiple processes or threads competing for a limited set of resources, such as shared memory, I/O devices, or locks. To prevent deadlocks, it is important to design systems that minimize resource contention and ensure that resources are released in a timely and predictable manner. Here are some strategies that can help prevent deadlocks:
- Avoid circular wait: Design the system to prevent circular waits, where each process is waiting for a resource that is held by another process in the circle.
- Resource allocation order: Specify an order for acquiring resources and make sure all processes follow the same order. This will ensure that resources are acquired in a predictable manner and can help prevent deadlocks.
- Use timeouts: Set timeouts for resource requests, so that if a process or thread is unable to acquire a resource within a certain time, it will release the resources it is holding and try again later.
- Resource preemption: If a process or thread is holding a resource that another process needs, the system can preempt the resource and give it to the waiting process. This can be done in a controlled manner to prevent resource starvation.
- Deadlock detection and recovery: Implement algorithms to detect deadlocks and recover from them. One common approach is to use a resource allocation graph to identify cycles and release resources in a way that breaks the cycle.
- How HashMap Internally Works
https://www.youtube.com/watch?v=1CJbB6SzjVw&t=1s
- ACID
https://stackoverflow.com/questions/3740280/how-do-acid-and-database-transactions-work
- Optimistic Locking is a strategy where you read a record, take note of a version number (other methods to do this involve dates, timestamps or checksums/hashes) and check that the version hasn't changed before you write the record back. When you write the record back you filter the update on the version to make sure it's atomic. (i.e. hasn't been updated between when you check the version and write the record to the disk) and update the version in one hit.If the record is dirty (i.e. different version to yours) you abort the transaction and the user can re-start it. This strategy is most applicable to high-volume systems and three-tier architectures where you do not necessarily maintain a connection to the database for your session. In this situation the client cannot actually maintain database locks as the connections are taken from a pool and you may not be using the same connection from one access to the next.
https://stackoverflow.com/questions/129329/optimistic-vs-pessimistic-locking
- Pessimistic Pessimistic Locking is when you lock the record for your exclusive use until you have finished with it. It has much better integrity than optimistic locking but requires you to be careful with your application design to avoid Deadlocks. To use pessimistic locking you need either a direct connection to the database (as would typically be the case in a two tier client server application) or an externally available transaction ID that can be used independently of the connection.
https://stackoverflow.com/questions/129329/optimistic-vs-pessimistic-locking
- Dependency injection
Dependency injection is a design pattern commonly used in Java programming to achieve loose coupling between classes and to promote code reusability. In dependency injection, an object's dependencies are provided to it rather than having the object create them itself.
public class MyClass {
private final MyDependency myDependency;
public MyClass(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
Dependency injection is basically providing the objects that an object needs (its dependencies) instead of having it construct them itself. It's a very useful technique for testing, since it allows dependencies to be mocked or stubbed out.
Dependencies can be injected into objects by many means (such as constructor injection or setter injection). One can even use specialized dependency injection frameworks (e.g. Spring) to do that, but they certainly aren't required. You don't need those frameworks to have dependency injection. Instantiating and passing objects (dependencies) explicitly is just as good an injection as injection by framework.
- What's the difference between INNER JOIN, LEFT JOIN, RIGHT JOIN and FULL JOIN?
- Builder Pattern
The Builder Pattern is a creational design pattern that is commonly used in Java programming. It is used to create complex objects by separating the construction of an object from its representation. The pattern involves the use of a separate builder class that is responsible for creating the desired object.
public class Person {
private final String name;
private final int age;
private final String address;
private final String phone;
private Person(PersonBuilder builder) {
this.name = builder.name;
this.age = builder.age;
this.address = builder.address;
this.phone = builder.phone;
}
public static class PersonBuilder {
private String name;
private int age;
private String address;
private String phone;
public PersonBuilder(String name, int age) {
this.name = name;
this.age = age;
}
public PersonBuilder address(String address) {
this.address = address;
return this;
}
public PersonBuilder phone(String phone) {
this.phone = phone;
return this;
}
public Person build() {
return new Person(this);
}
}
// getters and setters
}
To use the Builder Pattern, we would create a new Person object using the builder, like this:
Person person = new Person.PersonBuilder("John", 30)
.address("123 codebase.Main St")
.phone("555-555-1234")
.build();
- Singleton Pattern
public class MyClass{
private static Myclass instance;
private MyClass(){
//Private instantiation
}
public static synchronized MyClass getInstance() //If you want your method thread safe...
{
if (instance == null) {
instance = new MyClass();
}
return instance;
}
}
- Strategy Pattern
public interface PaymentStrategy {
void pay(double amount);
}
public class CreditCardPayment implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public CreditCardPayment(String name, String cardNumber, String cvv, String dateOfExpiry) {
this.name = name;
this.cardNumber = cardNumber;
this.cvv = cvv;
this.dateOfExpiry = dateOfExpiry;
}
public void pay(double amount) {
System.out.println(amount + " paid using credit card.");
}
}
public class PayPalPayment implements PaymentStrategy {
private String emailId;
private String password;
public PayPalPayment(String emailId, String password) {
this.emailId = emailId;
this.password = password;
}
public void pay(double amount) {
System.out.println(amount + " paid using PayPal.");
}
}
public class PaymentContext {
private PaymentStrategy paymentStrategy;
public PaymentContext(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void executePayment(double amount) {
paymentStrategy.pay(amount);
}
}
public class codebase.Main {
public static void main(String[] args) {
PaymentContext paymentContext = new PaymentContext(new CreditCardPayment("John Doe", "1234 5678 9012 3456", "123", "12/22"));
paymentContext.executePayment(100.0);
paymentContext = new PaymentContext(new PayPalPayment("[email protected]", "password123"));
paymentContext.executePayment(200.0);
}
}
- Facade Pattern
Suppose we have a complex subsystem with several classes that work together to perform a task. We can simplify the interface to this subsystem by creating a Facade class that encapsulates the complexity of the subsystem and provides a simpler interface to the clients.
Let's take an example of a computer system that has several components like CPU, RAM, HardDisk, etc. The Facade class will encapsulate the complexity of the subsystem and provide a simple interface to start the computer.
public class CPU {
public void processData() {
System.out.println("CPU is processing data...");
}
}
public class RAM {
public void load() {
System.out.println("RAM is loading data...");
}
}
public class HardDisk {
public void readData() {
System.out.println("HardDisk is reading data...");
}
}
public class ComputerFacade {
private CPU cpu;
private RAM ram;
private HardDisk hardDisk;
public ComputerFacade() {
cpu = new CPU();
ram = new RAM();
hardDisk = new HardDisk();
}
public void start() {
hardDisk.readData();
ram.load();
cpu.processData();
System.out.println("Computer has started successfully!");
}
}
public class codebase.Main {
public static void main(String[] args) {
ComputerFacade computer = new ComputerFacade();
computer.start();
}
}
- Comparable vs Comparator
- Are private methods testable?
Yes, it is possible to test private methods in Java using reflection. However, testing private methods should generally be avoided as it can make the tests brittle and difficult to maintain. Instead, you should focus on testing the public behavior of your classes.
public class MyClass {
private int privateMethod(int x) {
return x * 2;
}
}
and
import java.lang.reflect.Method;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class MyClassTest {
@Test
public void testPrivateMethod() throws Exception {
MyClass myObject = new MyClass();
Method privateMethod = MyClass.class.getDeclaredMethod("privateMethod", int.class);
privateMethod.setAccessible(true);
int result = (int) privateMethod.invoke(myObject, 3);
assertEquals(6, result);
}
}
- Stack
Java Collection Framework provides a class named “Stack”. This Stack class extends the Vector class and implements the functionality of the Stack data structure.
A stack data structure supports the following operations:
Push: Adds an element to the stack. As a result, the value of the top is incremented.
Pop: An element is removed from the stack. After the pop operation, the value of the top is decremented.
Peek: This operation is used to look up or search for an element. The value of the top is not modified.
https://www.techiedelight.com/stack-implementation-in-java/
- Queue
The Queue interface is present in java.util package and extends the Collection interface is used to hold the elements about to be processed in FIFO(First In First Out) order. It is an ordered list of objects with its use limited to inserting elements at the end of the list and deleting elements from the start of the list, (i.e.), it follows the FIFO or the First-In-First-Out principle.
Being an interface the queue needs a concrete class for the declaration and the most common classes are the PriorityQueue and LinkedList in Java. Note that neither of these implementations is thread-safe. PriorityBlockingQueue is one alternative implementation if the thread-safe implementation is needed.
- Type of references in Java
- Stron References - This is the default type/class of Reference Object. Any object which has an active strong reference are not eligible for garbage collection. The object is garbage collected only when the variable which was strongly referenced points to null.
MyClass obj = new MyClass ();
Here ‘obj’ object is strong reference to newly created instance of MyClass, currently obj is active object so can’t be garbage collected.
obj = null;
'obj' object is no longer referencing to the instance. So the 'MyClass type object is now available for garbage collection.
- Weak Reference
- Phantom References
Similarly to weak references, phantom references don't prohibit the garbage collector from enqueueing objects for being cleared. The difference is phantom references must be manually polled from the reference queue before they can be finalized. That means we can decide what we want to do before they are cleared.
- Soft References
- Race condition
In Java, a race condition occurs when two or more threads access a shared resource or variable simultaneously, and the result of the operation depends on the timing or order of execution of the threads. This can lead to unpredictable and erroneous behavior, as each thread may assume the resource or variable is in a particular state that may have been changed by another thread.
public class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class codebase.Main {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
- POST vs PUT vs PATCH
- LinkedList vs ArrayList
- SOLID
- What is the difference between an interface and abstract class?
Interface
Abstract class
- Singleton in synchronised way
public class Singleton {
private static Singleton instance;
private Singleton() {
// private constructor to prevent instantiation from outside the class
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- Exceptions in Java
All exception classes are subtypes of the java.lang.Exception class. The exception class is a subclass of the Throwable class. Other than the exception class there is another subclass called Error which is derived from the Throwable class.
Errors are abnormal conditions that happen in case of severe failures, these are not handled by the Java programs. Errors are generated to indicate errors generated by the runtime environment. Example: JVM is out of memory. Normally, programs cannot recover from errors.
The Exception class has two main subclasses: IOException class and RuntimeException Class.
- Primary Key
- Indexes in database
- String Pool
- Type Erasure
- HashSet vs LinkedHashSet
- Finding the middle element of Linked List
- Difference between FetchType LAZY and EAGER in Java Persistence API?
LAZY = fetch when needed EAGER = fetch immediately
- What is the solution for the N+1 issue in JPA and Hibernate?
- Pagination
- Sequence vs Identity
- Spring Bean Scopes
- singleton
- prototype
- request
- session
- application
- websocket
- Time Scheduling in Spring Boot
https://stackoverflow.com/questions/54341037/time-scheduling-in-spring-boot
- Difference between StringBuilder and StringBuffer
- Transactions Isolation
- ISOLATION_READ_UNCOMMITTED - Allows dirty reads.
- ISOLATION_READ_COMMITTED - Does not allow dirty reads.
- ISOLATION_REPEATABLE_READ - If a row is read twice in the same transaction, the result will always be the same.
- ISOLATION_SERIALIZABLE - Performs all transactions in a sequence.
- Phenomenons in SQL
- dirty read: a transaction reads data written by a concurrent uncommitted transaction
- non repeatable reads: A transaction re-reads data it has previously read and finds that data has been modified by another transaction (that committed since the initial read)
- phantom-read: A transaction re-executes a query returning a set of rows that satisfy a search condition and finds that the set of rows satisfying the condition has changed due to another recently-committed transaction
- serialization anomaly: The result of successfully committing a group of transactions is inconsistent with all possible orderings of running those transactions one at a time.
- Hashset vs Treeset
- Difference between HashMap, LinkedHashMap and TreeMap
- @Qualifier
This annotation may be used on a field or parameter as a qualifier for candidate beans when autowiring. It may also be used to annotate other custom annotations that can then in turn be used as qualifiers.
- SOAP vs REST (differences)
- GROUP BY and HAVING
In SQL, "GROUP BY" and "HAVING" are both clauses that are used in conjunction with the SELECT statement to filter and aggregate data. While both clauses are related to filtering data based on a condition, they have different functions.
The "GROUP BY" clause is used to group the result set by one or more columns. This is useful when you want to aggregate data based on a specific category. For example, if you have a table of sales data, you might use the GROUP BY clause to group the data by product category to see the total sales for each category:
SELECT product_category, SUM(sales) FROM sales_table GROUP BY product_category;
The "HAVING" clause is used to filter the results of the GROUP BY clause. It allows you to specify a condition that must be met for the grouped data to be included in the result set. For example, you might use the HAVING clause to filter the results of the previous example to only include categories where the total sales are greater than a certain amount:
SELECT product_category, SUM(sales) FROM sales_table GROUP BY product_category HAVING SUM(sales) > 100000;
49.UNION
In SQL, the "UNION" operator is used to combine the result sets of two or more SELECT statements into a single result set. The result set returned by the UNION operator contains all the distinct rows from the combined result sets of the SELECT statements.
The syntax for the UNION operator is as follows:
SELECT column1, column2, ... FROM table1
UNION
SELECT column1, column2, ... FROM table2
[UNION ...];
- Java 8 Functional Interface
- Semaphores
A counting semaphore. Conceptually, a semaphore maintains a set of permits. Each acquire() blocks if necessary until a permit is available, and then takes it. Each release() adds a permit, potentially releasing a blocking acquirer. However, no actual permit objects are used; the Semaphore just keeps a count of the number available and acts accordingly. Semaphores are often used to restrict the number of threads than can access some (physical or logical) resource.
-
Future https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html
-
FutureTask https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/FutureTask.html
-
Callable https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html
-
Runnable https://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html
- The difference between the Runnable and Callable interfaces in Java?
- Liskov substitution
class Shape {
int area() {
return 0;
}
}
class Rectangle extends Shape {
int width;
int height;
@Override
int area() {
return width * height;
}
}
class Square extends Shape {
int side;
@Override
int area() {
return side * side;
}
}
class AreaCalculator {
int calculateArea(Shape shape) {
return shape.area();
}
}
public class LiskovSubstitutionExample {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.width = 5;
rectangle.height = 10;
Square square = new Square();
square.side = 5;
AreaCalculator calculator = new AreaCalculator();
int rectangleArea = calculator.calculateArea(rectangle);
int squareArea = calculator.calculateArea(square);
System.out.println("Rectangle area: " + rectangleArea);
System.out.println("Square area: " + squareArea);
}
}
- Dependency inversion principle
public interface Database {
void saveData(String data);
}
public class MySqlDatabase implements Database {
public void saveData(String data) {
System.out.println("Saving " + data + " to MySQL database");
}
}
public class PostgresDatabase implements Database {
public void saveData(String data) {
System.out.println("Saving " + data + " to Postgres database");
}
}
public class OrderProcessor {
private final Database database;
public OrderProcessor(Database database) {
this.database = database;
}
public void processOrder(String orderData) {
// Process order
database.saveData(orderData);
}
}
public class codebase.Main {
public static void main(String[] args) {
// Create a MySQL database instance and use it to process an order
Database mySqlDatabase = new MySqlDatabase();
OrderProcessor orderProcessor = new OrderProcessor(mySqlDatabase);
orderProcessor.processOrder("Order data");
// Create a Postgres database instance and use it to process another order
Database postgresDatabase = new PostgresDatabase();
orderProcessor = new OrderProcessor(postgresDatabase);
orderProcessor.processOrder("Another order data");
}
}
- Difference between PreparedStatement and CallableStatement
- What exactly is bucket in hashmap?
- Difference between volatile and synchronized in Java
https://stackoverflow.com/questions/3519664/difference-between-volatile-and-synchronized-in-java
- Life Cycle of a Spring Bean
- OAuth vs JWT
OAuth and JWT are both protocols used for authentication and authorization in web applications, but they serve different purposes.
OAuth (Open Authorization) is an authorization protocol used for granting third-party applications access to a user's resources without sharing their login credentials. It allows users to grant permissions to external applications to access their data on behalf of them. OAuth provides a secure and standardized way to handle user authorization and access control. OAuth generates an access token that the client can use to access protected resources on behalf of the user. OAuth is commonly used for social login, allowing users to authenticate with their social media accounts and use the application without creating a new account.
JWT (JSON Web Token) is a format used to transmit information securely between parties as a JSON object. It's commonly used for authentication and authorization, like OAuth, but it differs in that it is self-contained and does not require server-side storage of session data. JWT contains a set of claims, which are statements about the user, encoded as a JSON object, and signed using a secret key. The signature ensures that the token is authentic and has not been tampered with. The most common use case for JWT is stateless authentication, where the user's credentials are validated once, and a token is issued that the client can use to authenticate all subsequent requests.
In summary, OAuth is primarily used for delegating authorization to third-party applications, while JWT is used for stateless authentication and authorization.
- Extending Exception/RunTimeException in Java?
public class ValidationException extends RuntimeException {
}
and
public class ValidationException extends Exception {
}
- The JPA and Hibernate first-level cache
https://vladmihalcea.com/jpa-hibernate-first-level-cache/
- The JPA and Hibernate first-level cache
- Partitioning in databases :
-
SQL Server Table Partitioning https://www.sqlservertutorial.net/sql-server-administration/sql-server-table-partitioning/
- What does the spring annotation @ConditionalOnMissingBean do?
- Kafka Delivery Semantics
- How does garbage collection work?
In computer science, garbage collection (GC) is a form of automatic memory management. The garbage collector attempts to reclaim memory which was allocated by the program, but is no longer referenced; such memory is called garbage. Garbage collection was invented by American computer scientist John McCarthy around 1959 to simplify manual memory management in Lisp. Garbage collection relieves the programmer from doing manual memory management, where the programmer specifies what objects to de-allocate and return to the memory system and when to do so. Other, similar techniques include stack allocation, region inference, and memory ownership, and combinations thereof. Garbage collection may take a significant proportion of a program's total processing time, and affect performance as a result. Resources other than memory, such as network sockets, database handles, windows, file descriptors, and device descriptors, are not typically handled by garbage collection, but rather by other methods (e.g. destructors). Some such methods de-allocate memory also.
- how does garbage collection work?
- how do you check memory usage in java? Profiling?
- what are disadvantages of inheritance?
- what are the transaction scopes?
- Give examples of Aop
- Lifecycle of JPA object
- How would you dynamically create queries in Hibernate?
- CQRS
- Saga patern
- Difference between Orchestration vs Coreography
- They asked for some examples of real life usages of SOLID and design patterns
- lifecycle beans
- https://www.baeldung.com/hibernate-entity-lifecycle
- Dirty checking hibernate
- ON DELETE CASCADE
- https://stackoverflow.com/questions/5360795/what-is-the-difference-between-unidirectional-and-bidirectional-jpa-and-hibernat
- https://stackoverflow.com/questions/19857008/extending-exception-runtimeexception-in-java
- https://stackoverflow.com/questions/46455325/whats-the-difference-between-fetchgraph-and-loadgraph-in-jpa-2-1
- https://stackoverflow.com/questions/47571117/what-is-the-difference-between-completionstage-and-completablefuture
na mid/senior miałem takie m.in.: na mid/senior miałem tak:
- kontrakt hashCode&equals
- hashmapa - jak działa dokładnie
- czym są streamy, definicja, operation types
- czym są lambdy i jak wyglądałoby gdybyśmy ich nie użyli (czy w ogóle można xD)
- try with resources
- StringBuilder vs StringBuffer
- wzorce projektowe (które lubisz, których używasz, strategia)
- hibernate n+1
- eager vs lazy loading, kiedy lazy jest domyślne
- java pass by value czy reference
- co siedzi w środku Collections
- checked vs unchecked exceptions
- ArrayList vs LinkedList
- co to jest refleksja
- bean scope'y, jakich używać do wielowątkowości
- synchronized
- volatile
- optimistic vs pessimistic locking, jak zaimplementować pessimistic w Hibernate
- rekordy w Javie
- interfejs funkcyjny
- spring proxy, InvocationHandler
- adnotacje transakcji (czyli głównie omówić @Transactional)
- propagacje transakcji
- izolacje transakcji
- comparator vs comparable
- flow zapytania HTTP
- jakie cechy musi spełniać interfejs, żeby był RESTful
- co to w ogóle jest hibernate?
- czy możemy łapać wyjąki unchecked
- grupowanie danych (krótkie zadanko z Collectors.groupingBy)
- optymalizacja zapytań SQL
- co to jest indeks w bazie danych
- joiny, omówić różnice
- join vs union all
- immutable i stateless
- monolit vs mikroserwisy
- sql vs nosql
- git rebase vs merge, cherry pick
- fail safety i fault tolerance w mikro
- the outbox pattern
- saga
- co to jest DDD i architektura heksagonalna
- treeset vs hashset
- @ManyToMany, jak zaimplementować i jak to działa w Hibernate (JoinTable)
- thread vs process
- pula wątków
- circuit breaker
- retry
- load balancing
- service discovery
- rodzaje DI w Springu
- 2PC
- czy da się apkę zdebugować na produkcji
- SOLID -> liskov
- phantom reads